openvibers/openvibe.contracts Commits

Product catalog in Contracts (plan T11 lane D): registry.service-manifest 1.1.0 site showcase fields and registry.product@1

OP OpenVibers committed 1 Oct 2026 · 89c6e7d9a6 · 54 files, +3079 −16

The OpenVibe.Sites catalog (sites.json, 33 domains) gets one home per domain:
the site block of the ten service manifests that present their catalog domain
(news, reviews, tips, vip, trade, host, deals, coupons, openre, space) and
manifests/products/<domain>.json for the other 23. Fields copied from Sites
origin/main c2fda6c. contracts.products and test/product-catalog.test.js
assert every snapshotted Sites domain has exactly one home. Additive.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex df51a6b5..22437262 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -10,6 +10,22 @@ Add nine public `platform.*@1` schemas: usage and telemetry samples; node capabi
service instances; runtime, storage, delivery, agent, and harness offers. Each has valid and
invalid fixtures. ADR-034 is proposed. Generated types and bundle include all nine.
++## Unreleased — the product catalog (plan T11 lane D)
++
++The OpenVibe.Sites catalog (`sites.json`, 33 domains) moves into Contracts so Sites can be deleted. Additive.
++- `registry.service-manifest` 1.1.0: `site` gains the optional `tld`, `accent`, `description`, `pillars`, `faq`,
++ `keywords`, `vision`, `highlight`, `launch` and `relationships` (`service`, `plannedRepo`, `noRepo`, `linksLead`,
++ `links`, `meanwhile`, `statusApi`), defined once in `registry.product@1`'s `$defs`. Filled from the catalog for the
++ ten services whose site presents their catalog domain: news, reviews, tips, vip, trade, host, deals, coupons,
++ openre (openre.stream) and space.
++- `registry.product@1` (new) and `manifests/products/<domain>.json` for the other 23 catalog domains, with no
++ invented service manifests: the Network sub-domains (auth, api, admin, themes), the media-hub brands (video, pics,
++ download), ai.openvibe.network (moved) and ai.openvibe.services, realtime (closed), status, openvibe.events and the
++ planned products (food, quest, rent, homes, services, run, website, help, bot, watch, actor).
++ `relationships.service` names the manifest that serves a domain when one does.
++- `contracts.products` (`manifests`, `get`, `siteDomain`, `catalog()`); `test/product-catalog.test.js` asserts each
++ of the 33 snapshotted Sites domains has exactly one home and every home validates.
++
## 0.83.0 — 2026-09-29
**Host site configuration** (plan T12 J3): the capability `host.site.config` (active, first-party, `host:write`, project
ddiff --git a/README.md b/README.md
iindex 4c323c77..8f945d83 100644
---- a/README.md
++++ b/README.md
@@@ -69,6 +69,8 @@ Manifests: `manifests/services/` (30 services as of v0.34.0: 24 `alpha`, 1 `beta
## User modules (v0.4)
++`manifests/products/<domain>.json` (`registry.product@1`) is the product catalog the OpenVibe.Sites repository used to hold (plan T11 lane D): one file per product domain that no service manifest presents as a site (planned products, a service's sub-domains and extra brand domains, closed or moved addresses), with its tld, accent, description, pillars, FAQ, keywords, vision, launch requirement and relationships. A service manifest whose `site` carries `tld` (`registry.service-manifest` 1.1.0) is the catalog home of its own domain instead. `contracts.products.catalog()` lists both homes; every domain has exactly one (`test/product-catalog.test.js`, against the 33-domain Sites snapshot in `test/fixtures/sites-domains.json`).
++
`manifests/namespaces/*.json` define per-subject module namespaces (`modules.namespace@1`): owner, data schema, writers (`owner` service and/or the `user`), public fields, quota and what happens when the owner is retired. OpenVibe.Network stores the records (`modules.module-record@1`, revision-checked writes). `contracts.modules.validateData / publicView / canWrite` apply the same rules everywhere. Modules hold portable preferences and summaries, never domain truth, money or authoritative game inventory.
Every change to a record is announced as `network.module.updated` (v0.32): the revision after the change, the changed field names and only the new values of changed public fields. Removing an account deletes its records; merging two keeps the survivor's record where both have one and moves the rest. `onOwnerRemoved` is about the owning service being retired, not the person.
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex aaa63a6a..9857f794 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -89,13 +89,22 @@
},
{
"id": "registry.service-manifest",
-- "version": "1.0.0",
++ "version": "1.1.0",
"owner": "network",
"schema": "registry/service-manifest.v1.json",
"visibility": "public",
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "registry.product",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "registry/product.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "capabilities.capability",
"version": "1.0.0",
ddiff --git a/contracts/registry/product.v1.json b/contracts/registry/product.v1.json
nnew file mode 100644
iindex 00000000..622791f6
---- /dev/null
++++ b/contracts/registry/product.v1.json
@@@ -0,0 +1,99 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/registry/product.v1.json",
++ "title": "Product",
++ "description": "A product domain of the network's catalog (manifests/products/<domain>.json) that has no service manifest presenting it as a site: a planned product, a sub-domain of a service, a brand domain of a multi-domain service, a closed or moved address. Carries what the network's showcase pages need, copied from the OpenVibe.Sites catalog (plan T11 lane D) so Sites can be deleted. A product whose service manifest has a site block carries the same fields there (registry.service-manifest 1.1.0 site); every catalog domain has exactly one of the two homes. The $defs are shared with that site block.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": ["domain", "tld", "name", "icon", "accent", "tagline", "description", "keywords"],
++ "properties": {
++ "domain": { "type": "string", "pattern": "^[a-z0-9-]+(\\.[a-z0-9-]+)+$", "description": "The product's host, unique across the catalog." },
++ "tld": { "$ref": "#/$defs/tld" },
++ "name": { "type": "string", "maxLength": 60, "description": "Full name, e.g. OpenVibe.Food." },
++ "icon": { "type": "string", "pattern": "^[a-z0-9-]+$", "description": "Icon name of the showcase pages (a Font Awesome name such as fa-utensils)." },
++ "accent": { "$ref": "#/$defs/accent" },
++ "tagline": { "type": "string", "maxLength": 120 },
++ "description": { "$ref": "#/$defs/description" },
++ "legalProfile": {
++ "enum": ["streaming", "tools", "ugc", "games", "hosting", "account", "info"],
++ "description": "Which legal wording its /terms, /privacy and /dmca use (openvibe-shared/legal), as in a service manifest's site."
++ },
++ "kind": {
++ "enum": ["closed", "moved", "status"],
++ "description": "Absent for a product page. closed: the address was decided against and points elsewhere; moved: the product lives at another address; status: the address shows the network's status. relationships.links say where."
++ },
++ "pillars": { "$ref": "#/$defs/pillars" },
++ "faq": { "$ref": "#/$defs/faq" },
++ "keywords": { "$ref": "#/$defs/keywords" },
++ "vision": { "$ref": "#/$defs/vision" },
++ "highlight": { "$ref": "#/$defs/highlight" },
++ "launch": { "$ref": "#/$defs/launch" },
++ "relationships": { "$ref": "#/$defs/relationships" }
++ },
++ "$defs": {
++ "tld": { "type": "string", "pattern": "^[a-z0-9-]+$", "description": "The product's short name used everywhere (frame service names, /shared paths), e.g. food for openvibe.food. Not unique: a moved product keeps it on both addresses." },
++ "accent": { "type": "string", "pattern": "^#[0-9a-f]{6}$", "description": "Brand colour, lower-case hex." },
++ "description": { "type": "string", "minLength": 1, "maxLength": 600, "description": "The catalog's description, verbatim." },
++ "keywords": { "type": "string", "maxLength": 400, "description": "Comma-separated search keywords, verbatim." },
++ "launch": { "type": "string", "minLength": 1, "maxLength": 200, "description": "What a person still has to do or decide before the product launches. Absent once nothing blocks it." },
++ "text": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": ["title", "text"],
++ "properties": { "title": { "type": "string", "minLength": 1, "maxLength": 80 }, "text": { "type": "string", "minLength": 1, "maxLength": 300 } }
++ },
++ "pillars": { "type": "array", "items": { "$ref": "#/$defs/text" }, "maxItems": 8, "description": "Feature pillars, in display order." },
++ "faq": {
++ "type": "array",
++ "maxItems": 20,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": ["question", "answer"],
++ "properties": { "question": { "type": "string", "minLength": 1, "maxLength": 200 }, "answer": { "type": "string", "minLength": 1, "maxLength": 1000 } }
++ }
++ },
++ "link": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": ["label", "url"],
++ "properties": {
++ "label": { "type": "string", "minLength": 1, "maxLength": 80 },
++ "url": { "type": "string", "format": "uri", "pattern": "^https://" },
++ "note": { "type": "string", "maxLength": 300 }
++ }
++ },
++ "highlight": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": ["title", "text"],
++ "description": "A featured block on the product page.",
++ "properties": {
++ "title": { "type": "string", "minLength": 1, "maxLength": 120 },
++ "text": { "type": "string", "minLength": 1, "maxLength": 600 },
++ "points": { "type": "array", "items": { "type": "string", "maxLength": 200 }, "maxItems": 10 },
++ "cta": { "$ref": "#/$defs/link" },
++ "more": { "$ref": "#/$defs/link" }
++ }
++ },
++ "vision": {
++ "type": "object",
++ "additionalProperties": true,
++ "description": "The product page's vision section (badge, call to action, design preview, steps, choices, features, connections), kept verbatim from the catalog: a presentation payload the showcase renders as it is, not a domain contract."
++ },
++ "relationships": {
++ "type": "object",
++ "additionalProperties": false,
++ "description": "How the product relates to services, repositories and other pages.",
++ "properties": {
++ "service": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$", "description": "The service manifest (manifests/services/<id>.json) that serves or will serve this product, when one exists." },
++ "plannedRepo": { "type": "string", "pattern": "^[A-Za-z0-9.-]+$", "description": "The repository planned or used for the product (under github.com/OpenVibers)." },
++ "noRepo": { "const": true, "description": "No repository exists for the product yet." },
++ "linksLead": { "type": "string", "maxLength": 200, "description": "The sentence introducing links." },
++ "links": { "type": "array", "items": { "$ref": "#/$defs/link" }, "maxItems": 10, "description": "Where to go instead (a closed, moved or status address)." },
++ "meanwhile": { "type": "array", "items": { "$ref": "#/$defs/link" }, "maxItems": 10, "description": "What already works elsewhere on the network while the product is not launched." },
++ "statusApi": { "type": "string", "format": "uri", "pattern": "^https://", "description": "The status API a status page reads." }
++ }
++ }
++ }
++}
ddiff --git a/contracts/registry/service-manifest.v1.json b/contracts/registry/service-manifest.v1.json
iindex cfe7d4b5..43ff4bdd 100644
---- a/contracts/registry/service-manifest.v1.json
++++ b/contracts/registry/service-manifest.v1.json
@@@ -117,7 +117,7 @@
"what",
"legalProfile"
],
-- "description": "How the network presents the service as a site people visit (the navigation, the network home page, legal pages). A service without it is not a site.",
++ "description": "How the network presents the service as a site people visit (the navigation, the network home page, legal pages). A service without it is not a site. 1.1.0 (plan T11 lane D) adds the optional showcase fields copied from the OpenVibe.Sites catalog (tld, accent, description, pillars, faq, keywords, vision, highlight, launch, relationships; defined in registry.product@1): a site carrying tld is the catalog home of its domain (its publicOrigin host, or host), so that domain has no manifests/products file.",
"properties": {
"name": {
"type": "string",
@@@ -157,6 +157,36 @@
"type": "string",
"pattern": "^[a-z0-9.-]+$",
"description": "Only while the manifest has no publicOrigin."
++ },
++ "tld": {
++ "$ref": "product.v1.json#/$defs/tld"
++ },
++ "accent": {
++ "$ref": "product.v1.json#/$defs/accent"
++ },
++ "description": {
++ "$ref": "product.v1.json#/$defs/description"
++ },
++ "pillars": {
++ "$ref": "product.v1.json#/$defs/pillars"
++ },
++ "faq": {
++ "$ref": "product.v1.json#/$defs/faq"
++ },
++ "keywords": {
++ "$ref": "product.v1.json#/$defs/keywords"
++ },
++ "vision": {
++ "$ref": "product.v1.json#/$defs/vision"
++ },
++ "highlight": {
++ "$ref": "product.v1.json#/$defs/highlight"
++ },
++ "launch": {
++ "$ref": "product.v1.json#/$defs/launch"
++ },
++ "relationships": {
++ "$ref": "product.v1.json#/$defs/relationships"
}
}
},
ddiff --git a/fixtures/registry.product/invalid/accent-not-hex.json b/fixtures/registry.product/invalid/accent-not-hex.json
nnew file mode 100644
iindex 00000000..e42f4611
---- /dev/null
++++ b/fixtures/registry.product/invalid/accent-not-hex.json
@@@ -0,0 +1,44 @@
++{
++ "domain": "openvibe.food",
++ "tld": "food",
++ "name": "OpenVibe.Food",
++ "icon": "fa-utensils",
++ "accent": "orange",
++ "tagline": "What should we eat?",
++ "description": "OpenVibe.Food will bring food together: compare delivery prices across apps, find coupons and budget meal kits, plan meals alone, as a couple or as a group, and turn what is in your cupboard into recipes with the network's AI. The food tools on OpenVibe.Tools will live here too.",
++ "pillars": [
++ {
++ "title": "Compare delivery",
++ "text": "One search across delivery apps, with prices side by side."
++ },
++ {
++ "title": "Kits & boxes",
++ "text": "Meal kits and snack boxes, budget-friendly picks first."
++ },
++ {
++ "title": "Plan together",
++ "text": "Shared plans and chats for couples, households and groups."
++ },
++ {
++ "title": "Cupboard recipes",
++ "text": "Tell it what you have; get ideas that use it."
++ }
++ ],
++ "keywords": "openvibe food, compare food delivery prices, meal kits, meal planning, recipe ideas, grocery tools",
++ "launch": "the food tools moving over from OpenVibe.Tools",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "Food tools",
++ "url": "https://food.openvibe.tools",
++ "note": "The food and grocery tools work today."
++ },
++ {
++ "label": "OpenVibe.Coupons",
++ "url": "https://openvibe.coupons",
++ "note": "Coupons are the sibling room."
++ }
++ ]
++ }
++}
ddiff --git a/fixtures/registry.product/invalid/link-not-https.json b/fixtures/registry.product/invalid/link-not-https.json
nnew file mode 100644
iindex 00000000..b7fc681f
---- /dev/null
++++ b/fixtures/registry.product/invalid/link-not-https.json
@@@ -0,0 +1,37 @@
++{
++ "domain": "openvibe.food",
++ "tld": "food",
++ "name": "OpenVibe.Food",
++ "icon": "fa-utensils",
++ "accent": "#fb923c",
++ "tagline": "What should we eat?",
++ "description": "OpenVibe.Food will bring food together: compare delivery prices across apps, find coupons and budget meal kits, plan meals alone, as a couple or as a group, and turn what is in your cupboard into recipes with the network's AI. The food tools on OpenVibe.Tools will live here too.",
++ "pillars": [
++ {
++ "title": "Compare delivery",
++ "text": "One search across delivery apps, with prices side by side."
++ },
++ {
++ "title": "Kits & boxes",
++ "text": "Meal kits and snack boxes, budget-friendly picks first."
++ },
++ {
++ "title": "Plan together",
++ "text": "Shared plans and chats for couples, households and groups."
++ },
++ {
++ "title": "Cupboard recipes",
++ "text": "Tell it what you have; get ideas that use it."
++ }
++ ],
++ "keywords": "openvibe food, compare food delivery prices, meal kits, meal planning, recipe ideas, grocery tools",
++ "launch": "the food tools moving over from OpenVibe.Tools",
++ "relationships": {
++ "meanwhile": [
++ {
++ "label": "Tools",
++ "url": "http://openvibe.tools"
++ }
++ ]
++ }
++}
ddiff --git a/fixtures/registry.product/invalid/missing-tld.json b/fixtures/registry.product/invalid/missing-tld.json
nnew file mode 100644
iindex 00000000..013b9aa5
---- /dev/null
++++ b/fixtures/registry.product/invalid/missing-tld.json
@@@ -0,0 +1,43 @@
++{
++ "domain": "openvibe.food",
++ "name": "OpenVibe.Food",
++ "icon": "fa-utensils",
++ "accent": "#fb923c",
++ "tagline": "What should we eat?",
++ "description": "OpenVibe.Food will bring food together: compare delivery prices across apps, find coupons and budget meal kits, plan meals alone, as a couple or as a group, and turn what is in your cupboard into recipes with the network's AI. The food tools on OpenVibe.Tools will live here too.",
++ "pillars": [
++ {
++ "title": "Compare delivery",
++ "text": "One search across delivery apps, with prices side by side."
++ },
++ {
++ "title": "Kits & boxes",
++ "text": "Meal kits and snack boxes, budget-friendly picks first."
++ },
++ {
++ "title": "Plan together",
++ "text": "Shared plans and chats for couples, households and groups."
++ },
++ {
++ "title": "Cupboard recipes",
++ "text": "Tell it what you have; get ideas that use it."
++ }
++ ],
++ "keywords": "openvibe food, compare food delivery prices, meal kits, meal planning, recipe ideas, grocery tools",
++ "launch": "the food tools moving over from OpenVibe.Tools",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "Food tools",
++ "url": "https://food.openvibe.tools",
++ "note": "The food and grocery tools work today."
++ },
++ {
++ "label": "OpenVibe.Coupons",
++ "url": "https://openvibe.coupons",
++ "note": "Coupons are the sibling room."
++ }
++ ]
++ }
++}
ddiff --git a/fixtures/registry.product/invalid/pillars-as-tuples.json b/fixtures/registry.product/invalid/pillars-as-tuples.json
nnew file mode 100644
iindex 00000000..294a175b
---- /dev/null
++++ b/fixtures/registry.product/invalid/pillars-as-tuples.json
@@@ -0,0 +1,32 @@
++{
++ "domain": "openvibe.food",
++ "tld": "food",
++ "name": "OpenVibe.Food",
++ "icon": "fa-utensils",
++ "accent": "#fb923c",
++ "tagline": "What should we eat?",
++ "description": "OpenVibe.Food will bring food together: compare delivery prices across apps, find coupons and budget meal kits, plan meals alone, as a couple or as a group, and turn what is in your cupboard into recipes with the network's AI. The food tools on OpenVibe.Tools will live here too.",
++ "pillars": [
++ [
++ "Compare delivery",
++ "One search across delivery apps."
++ ]
++ ],
++ "keywords": "openvibe food, compare food delivery prices, meal kits, meal planning, recipe ideas, grocery tools",
++ "launch": "the food tools moving over from OpenVibe.Tools",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "Food tools",
++ "url": "https://food.openvibe.tools",
++ "note": "The food and grocery tools work today."
++ },
++ {
++ "label": "OpenVibe.Coupons",
++ "url": "https://openvibe.coupons",
++ "note": "Coupons are the sibling room."
++ }
++ ]
++ }
++}
ddiff --git a/fixtures/registry.product/invalid/unknown-kind.json b/fixtures/registry.product/invalid/unknown-kind.json
nnew file mode 100644
iindex 00000000..ea7ed4b9
---- /dev/null
++++ b/fixtures/registry.product/invalid/unknown-kind.json
@@@ -0,0 +1,45 @@
++{
++ "domain": "openvibe.food",
++ "tld": "food",
++ "name": "OpenVibe.Food",
++ "icon": "fa-utensils",
++ "accent": "#fb923c",
++ "tagline": "What should we eat?",
++ "description": "OpenVibe.Food will bring food together: compare delivery prices across apps, find coupons and budget meal kits, plan meals alone, as a couple or as a group, and turn what is in your cupboard into recipes with the network's AI. The food tools on OpenVibe.Tools will live here too.",
++ "pillars": [
++ {
++ "title": "Compare delivery",
++ "text": "One search across delivery apps, with prices side by side."
++ },
++ {
++ "title": "Kits & boxes",
++ "text": "Meal kits and snack boxes, budget-friendly picks first."
++ },
++ {
++ "title": "Plan together",
++ "text": "Shared plans and chats for couples, households and groups."
++ },
++ {
++ "title": "Cupboard recipes",
++ "text": "Tell it what you have; get ideas that use it."
++ }
++ ],
++ "keywords": "openvibe food, compare food delivery prices, meal kits, meal planning, recipe ideas, grocery tools",
++ "launch": "the food tools moving over from OpenVibe.Tools",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "Food tools",
++ "url": "https://food.openvibe.tools",
++ "note": "The food and grocery tools work today."
++ },
++ {
++ "label": "OpenVibe.Coupons",
++ "url": "https://openvibe.coupons",
++ "note": "Coupons are the sibling room."
++ }
++ ]
++ },
++ "kind": "archived"
++}
ddiff --git a/fixtures/registry.product/valid/moved.json b/fixtures/registry.product/valid/moved.json
nnew file mode 100644
iindex 00000000..a7342205
---- /dev/null
++++ b/fixtures/registry.product/valid/moved.json
@@@ -0,0 +1,29 @@
++{
++ "domain": "ai.openvibe.network",
++ "tld": "ai",
++ "name": "ai.openvibe.network",
++ "icon": "fa-robot",
++ "accent": "#c084fc",
++ "tagline": "Moved: OpenVibe.AI's public home is ai.openvibe.services.",
++ "legalProfile": "info",
++ "kind": "moved",
++ "description": "OpenVibe.AI's public address is now ai.openvibe.services, next to the rest of the network's programmable services on OpenVibe.Services. This address serves only this notice until every client has moved; then it redirects.",
++ "keywords": "openvibe ai, openvibe network ai, openvibe platform",
++ "relationships": {
++ "service": "ai",
++ "plannedRepo": "OpenVibe.AI",
++ "linksLead": "OpenVibe.AI lives on OpenVibe.Services now.",
++ "links": [
++ {
++ "label": "ai.openvibe.services",
++ "url": "https://ai.openvibe.services/",
++ "note": "OpenVibe.AI: the gateway, templates, workflows and runs."
++ },
++ {
++ "label": "OpenVibe.Services",
++ "url": "https://openvibe.services/",
++ "note": "Every programmable OpenVibe service in one place."
++ }
++ ]
++ }
++}
ddiff --git a/fixtures/registry.product/valid/planned.json b/fixtures/registry.product/valid/planned.json
nnew file mode 100644
iindex 00000000..92d95c35
---- /dev/null
++++ b/fixtures/registry.product/valid/planned.json
@@@ -0,0 +1,44 @@
++{
++ "domain": "openvibe.food",
++ "tld": "food",
++ "name": "OpenVibe.Food",
++ "icon": "fa-utensils",
++ "accent": "#fb923c",
++ "tagline": "What should we eat?",
++ "description": "OpenVibe.Food will bring food together: compare delivery prices across apps, find coupons and budget meal kits, plan meals alone, as a couple or as a group, and turn what is in your cupboard into recipes with the network's AI. The food tools on OpenVibe.Tools will live here too.",
++ "pillars": [
++ {
++ "title": "Compare delivery",
++ "text": "One search across delivery apps, with prices side by side."
++ },
++ {
++ "title": "Kits & boxes",
++ "text": "Meal kits and snack boxes, budget-friendly picks first."
++ },
++ {
++ "title": "Plan together",
++ "text": "Shared plans and chats for couples, households and groups."
++ },
++ {
++ "title": "Cupboard recipes",
++ "text": "Tell it what you have; get ideas that use it."
++ }
++ ],
++ "keywords": "openvibe food, compare food delivery prices, meal kits, meal planning, recipe ideas, grocery tools",
++ "launch": "the food tools moving over from OpenVibe.Tools",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "Food tools",
++ "url": "https://food.openvibe.tools",
++ "note": "The food and grocery tools work today."
++ },
++ {
++ "label": "OpenVibe.Coupons",
++ "url": "https://openvibe.coupons",
++ "note": "Coupons are the sibling room."
++ }
++ ]
++ }
++}
ddiff --git a/fixtures/registry.service-manifest/invalid/site-catalog-bad-accent.json b/fixtures/registry.service-manifest/invalid/site-catalog-bad-accent.json
nnew file mode 100644
iindex 00000000..9f4a6100
---- /dev/null
++++ b/fixtures/registry.service-manifest/invalid/site-catalog-bad-accent.json
@@@ -0,0 +1,148 @@
++{
++ "version": "0.1.0",
++ "contractRanges": {
++ "openvibe-contracts": ">=0.19.0 <1.0.0"
++ },
++ "id": "news",
++ "name": "OpenVibe.News",
++ "status": "alpha",
++ "repository": "OpenVibers/OpenVibe.News",
++ "domains": [
++ "openvibe.news"
++ ],
++ "publicOrigin": "https://openvibe.news",
++ "health": "/api/health",
++ "ready": "/api/ready",
++ "internalOrigin": "http://127.0.0.1:4820",
++ "exposure": {
++ "state": "internal",
++ "publicSite": "placeholder",
++ "note": "the domain serves the OpenVibe.Sites placeholder page"
++ },
++ "site": {
++ "name": "News",
++ "icon": "news",
++ "tagline": "Sourced, not spun",
++ "what": "Source-backed stories: clustered coverage, cited summaries, multiple perspectives and timelines.",
++ "legalProfile": "info",
++ "position": 10,
++ "tld": "news",
++ "accent": "#FB923C",
++ "description": "OpenVibe.News aggregates the news with transparency: sourced stories, summaries that show their sources, multiple angles side by side, topic pages and framing you control — not an algorithm you can't see.",
++ "pillars": [
++ {
++ "title": "Sources first",
++ "text": "Every summary links to what it was built from."
++ },
++ {
++ "title": "Many angles",
++ "text": "Read the same story from several perspectives, side by side."
++ },
++ {
++ "title": "Your framing",
++ "text": "You choose the lens; nothing is chosen for you silently."
++ },
++ {
++ "title": "Topics",
++ "text": "Follow topics across sources, with your OpenVibe account."
++ }
++ ],
++ "keywords": "openvibe news, transparent news, news aggregation, multiple perspectives",
++ "launch": "a person to verify a news source's terms and an editor to publish the first reviewed story",
++ "relationships": {
++ "plannedRepo": "OpenVibe.News",
++ "meanwhile": [
++ {
++ "label": "Network updates",
++ "url": "https://openvibe.live/updates",
++ "note": "What is happening across OpenVibe."
++ }
++ ]
++ }
++ },
++ "capabilities": [
++ "news.story.create",
++ "news.story.read",
++ "news.story.revise",
++ "news.story.publish",
++ "news.story.retract",
++ "news.cluster.read",
++ "news.cluster.manage",
++ "news.source.attach",
++ "news.timeline.update",
++ "news.perspective.update",
++ "news.topic.manage"
++ ],
++ "eventsProduced": [
++ "news.source.ingested",
++ "news.source.failed",
++ "news.cluster.updated",
++ "news.story.created",
++ "news.story.flagged",
++ "news.story.published",
++ "news.story.updated",
++ "news.story.unpublished",
++ "news.story.retracted",
++ "news.index_document.upserted",
++ "news.index_document.deleted",
++ "news.moderation.action"
++ ],
++ "eventsConsumed": [
++ "sources.item.created",
++ "sources.item.updated",
++ "sources.item.removed",
++ "sources.fetch.failed"
++ ],
++ "namespacesOwned": [
++ "news.*"
++ ],
++ "lifecycle": {
++ "liveness": {
++ "endpoint": "/api/health",
++ "means": "the process is up and answers HTTP; it checks nothing else (the database and dependencies are what ready checks)"
++ },
++ "shutdown": {
++ "signal": "SIGTERM",
++ "deadlineSeconds": 5,
++ "drains": [
++ "worker stopped (Sources cursor pull)",
++ "HTTP server closed",
++ "outbox relay stopped (awaited)",
++ "store closed"
++ ]
++ },
++ "startupRecovery": {
++ "resumes": [
++ {
++ "kind": "consumer",
++ "what": "the OpenVibe.Sources change feed is pulled again from the stored cursor, so items published during the stop arrive (idempotent per item revision)"
++ },
++ {
++ "kind": "consumer",
++ "what": "OpenVibe.Events redelivers sources.item.* and sources.fetch.failed that were pending during the stop (at least once, deduped by event_id)"
++ },
++ {
++ "kind": "outbox",
++ "what": "unsent events in the outbox table go to OpenVibe.Events once the relay starts (at least once; Events dedupes by event_id)"
++ }
++ ]
++ },
++ "rollback": {
++ "conditions": [
++ "automatic: ovhost deploy puts the previous sha back and restarts again when the ready URL does not answer 2xx within ready.timeoutSeconds after the restart (exit 3)",
++ "manual: ovhost rollback <service> --to <sha>"
++ ],
++ "window": "automatic only while ovhost waits for readiness after the restart (30 s in host.example.json); manual afterwards, as long as no blocker applies",
++ "blockers": {
++ "none": "the schema code only adds (CREATE … IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
++ }
++ },
++ "contracts": {
++ "range": ">=0.19.0 <1.0.0"
++ },
++ "leases": {
++ "none": "no claims: one process owns news.db (better-sqlite3, single writer)"
++ }
++ },
++ "notes": "Port 4820. Owns news_topics, news_source_items, news_story_clusters, news_stories, news_story_revisions, news_story_sources, news_perspectives, news_timeline_entries, news_editorial_flags in its own SQLite (openvibe-publishing packages, prefixes news_story and news). Consumes OpenVibe.Sources (sources.item.read, optionally sources.source.read) through an Events subscription (sources.item.*, sources.fetch.failed; events.subscription.manage) and a cursor pull; OpenVibe.Network (SSO, identity.subject.resolve); optionally OpenVibe.AI (ai.run.create, namespace news) for news.summarize_story and news.compare_perspectives drafts; publishes through OpenVibe.Events (events.event.publish); OpenVibe.Search reads news.index_document.* from Events."
++}
ddiff --git a/fixtures/registry.service-manifest/valid/site-catalog.json b/fixtures/registry.service-manifest/valid/site-catalog.json
nnew file mode 100644
iindex 00000000..758b0945
---- /dev/null
++++ b/fixtures/registry.service-manifest/valid/site-catalog.json
@@@ -0,0 +1,148 @@
++{
++ "version": "0.1.0",
++ "contractRanges": {
++ "openvibe-contracts": ">=0.19.0 <1.0.0"
++ },
++ "id": "news",
++ "name": "OpenVibe.News",
++ "status": "alpha",
++ "repository": "OpenVibers/OpenVibe.News",
++ "domains": [
++ "openvibe.news"
++ ],
++ "publicOrigin": "https://openvibe.news",
++ "health": "/api/health",
++ "ready": "/api/ready",
++ "internalOrigin": "http://127.0.0.1:4820",
++ "exposure": {
++ "state": "internal",
++ "publicSite": "placeholder",
++ "note": "the domain serves the OpenVibe.Sites placeholder page"
++ },
++ "site": {
++ "name": "News",
++ "icon": "news",
++ "tagline": "Sourced, not spun",
++ "what": "Source-backed stories: clustered coverage, cited summaries, multiple perspectives and timelines.",
++ "legalProfile": "info",
++ "position": 10,
++ "tld": "news",
++ "accent": "#fb923c",
++ "description": "OpenVibe.News aggregates the news with transparency: sourced stories, summaries that show their sources, multiple angles side by side, topic pages and framing you control — not an algorithm you can't see.",
++ "pillars": [
++ {
++ "title": "Sources first",
++ "text": "Every summary links to what it was built from."
++ },
++ {
++ "title": "Many angles",
++ "text": "Read the same story from several perspectives, side by side."
++ },
++ {
++ "title": "Your framing",
++ "text": "You choose the lens; nothing is chosen for you silently."
++ },
++ {
++ "title": "Topics",
++ "text": "Follow topics across sources, with your OpenVibe account."
++ }
++ ],
++ "keywords": "openvibe news, transparent news, news aggregation, multiple perspectives",
++ "launch": "a person to verify a news source's terms and an editor to publish the first reviewed story",
++ "relationships": {
++ "plannedRepo": "OpenVibe.News",
++ "meanwhile": [
++ {
++ "label": "Network updates",
++ "url": "https://openvibe.live/updates",
++ "note": "What is happening across OpenVibe."
++ }
++ ]
++ }
++ },
++ "capabilities": [
++ "news.story.create",
++ "news.story.read",
++ "news.story.revise",
++ "news.story.publish",
++ "news.story.retract",
++ "news.cluster.read",
++ "news.cluster.manage",
++ "news.source.attach",
++ "news.timeline.update",
++ "news.perspective.update",
++ "news.topic.manage"
++ ],
++ "eventsProduced": [
++ "news.source.ingested",
++ "news.source.failed",
++ "news.cluster.updated",
++ "news.story.created",
++ "news.story.flagged",
++ "news.story.published",
++ "news.story.updated",
++ "news.story.unpublished",
++ "news.story.retracted",
++ "news.index_document.upserted",
++ "news.index_document.deleted",
++ "news.moderation.action"
++ ],
++ "eventsConsumed": [
++ "sources.item.created",
++ "sources.item.updated",
++ "sources.item.removed",
++ "sources.fetch.failed"
++ ],
++ "namespacesOwned": [
++ "news.*"
++ ],
++ "lifecycle": {
++ "liveness": {
++ "endpoint": "/api/health",
++ "means": "the process is up and answers HTTP; it checks nothing else (the database and dependencies are what ready checks)"
++ },
++ "shutdown": {
++ "signal": "SIGTERM",
++ "deadlineSeconds": 5,
++ "drains": [
++ "worker stopped (Sources cursor pull)",
++ "HTTP server closed",
++ "outbox relay stopped (awaited)",
++ "store closed"
++ ]
++ },
++ "startupRecovery": {
++ "resumes": [
++ {
++ "kind": "consumer",
++ "what": "the OpenVibe.Sources change feed is pulled again from the stored cursor, so items published during the stop arrive (idempotent per item revision)"
++ },
++ {
++ "kind": "consumer",
++ "what": "OpenVibe.Events redelivers sources.item.* and sources.fetch.failed that were pending during the stop (at least once, deduped by event_id)"
++ },
++ {
++ "kind": "outbox",
++ "what": "unsent events in the outbox table go to OpenVibe.Events once the relay starts (at least once; Events dedupes by event_id)"
++ }
++ ]
++ },
++ "rollback": {
++ "conditions": [
++ "automatic: ovhost deploy puts the previous sha back and restarts again when the ready URL does not answer 2xx within ready.timeoutSeconds after the restart (exit 3)",
++ "manual: ovhost rollback <service> --to <sha>"
++ ],
++ "window": "automatic only while ovhost waits for readiness after the restart (30 s in host.example.json); manual afterwards, as long as no blocker applies",
++ "blockers": {
++ "none": "the schema code only adds (CREATE … IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
++ }
++ },
++ "contracts": {
++ "range": ">=0.19.0 <1.0.0"
++ },
++ "leases": {
++ "none": "no claims: one process owns news.db (better-sqlite3, single writer)"
++ }
++ },
++ "notes": "Port 4820. Owns news_topics, news_source_items, news_story_clusters, news_stories, news_story_revisions, news_story_sources, news_perspectives, news_timeline_entries, news_editorial_flags in its own SQLite (openvibe-publishing packages, prefixes news_story and news). Consumes OpenVibe.Sources (sources.item.read, optionally sources.source.read) through an Events subscription (sources.item.*, sources.fetch.failed; events.subscription.manage) and a cursor pull; OpenVibe.Network (SSO, identity.subject.resolve); optionally OpenVibe.AI (ai.run.create, namespace news) for news.summarize_story and news.compare_perspectives drafts; publishes through OpenVibe.Events (events.event.publish); OpenVibe.Search reads news.index_document.* from Events."
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex db33bcc3..e0cb12c6 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -636,7 +636,7 @@
}
},
"registry.service-manifest": {
-- "version": "1.0.0",
++ "version": "1.1.0",
"owner": "network",
"status": "active",
"schema": {
@@@ -758,7 +758,7 @@
"what",
"legalProfile"
],
-- "description": "How the network presents the service as a site people visit (the navigation, the network home page, legal pages). A service without it is not a site.",
++ "description": "How the network presents the service as a site people visit (the navigation, the network home page, legal pages). A service without it is not a site. 1.1.0 (plan T11 lane D) adds the optional showcase fields copied from the OpenVibe.Sites catalog (tld, accent, description, pillars, faq, keywords, vision, highlight, launch, relationships; defined in registry.product@1): a site carrying tld is the catalog home of its domain (its publicOrigin host, or host), so that domain has no manifests/products file.",
"properties": {
"name": {
"type": "string",
@@@ -798,6 +798,36 @@
"type": "string",
"pattern": "^[a-z0-9.-]+$",
"description": "Only while the manifest has no publicOrigin."
++ },
++ "tld": {
++ "$ref": "product.v1.json#/$defs/tld"
++ },
++ "accent": {
++ "$ref": "product.v1.json#/$defs/accent"
++ },
++ "description": {
++ "$ref": "product.v1.json#/$defs/description"
++ },
++ "pillars": {
++ "$ref": "product.v1.json#/$defs/pillars"
++ },
++ "faq": {
++ "$ref": "product.v1.json#/$defs/faq"
++ },
++ "keywords": {
++ "$ref": "product.v1.json#/$defs/keywords"
++ },
++ "vision": {
++ "$ref": "product.v1.json#/$defs/vision"
++ },
++ "highlight": {
++ "$ref": "product.v1.json#/$defs/highlight"
++ },
++ "launch": {
++ "$ref": "product.v1.json#/$defs/launch"
++ },
++ "relationships": {
++ "$ref": "product.v1.json#/$defs/relationships"
}
}
},
@@@ -1236,6 +1266,293 @@
}
}
},
++ "registry.product": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/registry/product.v1.json",
++ "title": "Product",
++ "description": "A product domain of the network's catalog (manifests/products/<domain>.json) that has no service manifest presenting it as a site: a planned product, a sub-domain of a service, a brand domain of a multi-domain service, a closed or moved address. Carries what the network's showcase pages need, copied from the OpenVibe.Sites catalog (plan T11 lane D) so Sites can be deleted. A product whose service manifest has a site block carries the same fields there (registry.service-manifest 1.1.0 site); every catalog domain has exactly one of the two homes. The $defs are shared with that site block.",
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "domain",
++ "tld",
++ "name",
++ "icon",
++ "accent",
++ "tagline",
++ "description",
++ "keywords"
++ ],
++ "properties": {
++ "domain": {
++ "type": "string",
++ "pattern": "^[a-z0-9-]+(\\.[a-z0-9-]+)+$",
++ "description": "The product's host, unique across the catalog."
++ },
++ "tld": {
++ "$ref": "#/$defs/tld"
++ },
++ "name": {
++ "type": "string",
++ "maxLength": 60,
++ "description": "Full name, e.g. OpenVibe.Food."
++ },
++ "icon": {
++ "type": "string",
++ "pattern": "^[a-z0-9-]+$",
++ "description": "Icon name of the showcase pages (a Font Awesome name such as fa-utensils)."
++ },
++ "accent": {
++ "$ref": "#/$defs/accent"
++ },
++ "tagline": {
++ "type": "string",
++ "maxLength": 120
++ },
++ "description": {
++ "$ref": "#/$defs/description"
++ },
++ "legalProfile": {
++ "enum": [
++ "streaming",
++ "tools",
++ "ugc",
++ "games",
++ "hosting",
++ "account",
++ "info"
++ ],
++ "description": "Which legal wording its /terms, /privacy and /dmca use (openvibe-shared/legal), as in a service manifest's site."
++ },
++ "kind": {
++ "enum": [
++ "closed",
++ "moved",
++ "status"
++ ],
++ "description": "Absent for a product page. closed: the address was decided against and points elsewhere; moved: the product lives at another address; status: the address shows the network's status. relationships.links say where."
++ },
++ "pillars": {
++ "$ref": "#/$defs/pillars"
++ },
++ "faq": {
++ "$ref": "#/$defs/faq"
++ },
++ "keywords": {
++ "$ref": "#/$defs/keywords"
++ },
++ "vision": {
++ "$ref": "#/$defs/vision"
++ },
++ "highlight": {
++ "$ref": "#/$defs/highlight"
++ },
++ "launch": {
++ "$ref": "#/$defs/launch"
++ },
++ "relationships": {
++ "$ref": "#/$defs/relationships"
++ }
++ },
++ "$defs": {
++ "tld": {
++ "type": "string",
++ "pattern": "^[a-z0-9-]+$",
++ "description": "The product's short name used everywhere (frame service names, /shared paths), e.g. food for openvibe.food. Not unique: a moved product keeps it on both addresses."
++ },
++ "accent": {
++ "type": "string",
++ "pattern": "^#[0-9a-f]{6}$",
++ "description": "Brand colour, lower-case hex."
++ },
++ "description": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 600,
++ "description": "The catalog's description, verbatim."
++ },
++ "keywords": {
++ "type": "string",
++ "maxLength": 400,
++ "description": "Comma-separated search keywords, verbatim."
++ },
++ "launch": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200,
++ "description": "What a person still has to do or decide before the product launches. Absent once nothing blocks it."
++ },
++ "text": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "title",
++ "text"
++ ],
++ "properties": {
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "text": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 300
++ }
++ }
++ },
++ "pillars": {
++ "type": "array",
++ "items": {
++ "$ref": "#/$defs/text"
++ },
++ "maxItems": 8,
++ "description": "Feature pillars, in display order."
++ },
++ "faq": {
++ "type": "array",
++ "maxItems": 20,
++ "items": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "question",
++ "answer"
++ ],
++ "properties": {
++ "question": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "answer": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 1000
++ }
++ }
++ }
++ },
++ "link": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "label",
++ "url"
++ ],
++ "properties": {
++ "label": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "url": {
++ "type": "string",
++ "format": "uri",
++ "pattern": "^https://"
++ },
++ "note": {
++ "type": "string",
++ "maxLength": 300
++ }
++ }
++ },
++ "highlight": {
++ "type": "object",
++ "additionalProperties": false,
++ "required": [
++ "title",
++ "text"
++ ],
++ "description": "A featured block on the product page.",
++ "properties": {
++ "title": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 120
++ },
++ "text": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 600
++ },
++ "points": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "maxLength": 200
++ },
++ "maxItems": 10
++ },
++ "cta": {
++ "$ref": "#/$defs/link"
++ },
++ "more": {
++ "$ref": "#/$defs/link"
++ }
++ }
++ },
++ "vision": {
++ "type": "object",
++ "additionalProperties": true,
++ "description": "The product page's vision section (badge, call to action, design preview, steps, choices, features, connections), kept verbatim from the catalog: a presentation payload the showcase renders as it is, not a domain contract."
++ },
++ "relationships": {
++ "type": "object",
++ "additionalProperties": false,
++ "description": "How the product relates to services, repositories and other pages.",
++ "properties": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]*$",
++ "description": "The service manifest (manifests/services/<id>.json) that serves or will serve this product, when one exists."
++ },
++ "plannedRepo": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9.-]+$",
++ "description": "The repository planned or used for the product (under github.com/OpenVibers)."
++ },
++ "noRepo": {
++ "const": true,
++ "description": "No repository exists for the product yet."
++ },
++ "linksLead": {
++ "type": "string",
++ "maxLength": 200,
++ "description": "The sentence introducing links."
++ },
++ "links": {
++ "type": "array",
++ "items": {
++ "$ref": "#/$defs/link"
++ },
++ "maxItems": 10,
++ "description": "Where to go instead (a closed, moved or status address)."
++ },
++ "meanwhile": {
++ "type": "array",
++ "items": {
++ "$ref": "#/$defs/link"
++ },
++ "maxItems": 10,
++ "description": "What already works elsewhere on the network while the product is not launched."
++ },
++ "statusApi": {
++ "type": "string",
++ "format": "uri",
++ "pattern": "^https://",
++ "description": "The status API a status page reads."
++ }
++ }
++ }
++ }
++ }
++ },
"capabilities.capability": {
"version": "1.0.0",
"owner": "network",
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 90ee3224..ad0864d6 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -224,7 +224,7 @@ export interface Problem {
[k: string]: unknown | undefined;
}
--/** [email protected] (owner: network) */
++/** [email protected] (owner: network) */
/**
* What a service is, where it lives and what it offers. The ecosystem registry is built from these, not from hard-coded route maps.
*/
@@@ -262,7 +262,7 @@ export interface ServiceManifest {
repo?: string;
};
/**
-- * How the network presents the service as a site people visit (the navigation, the network home page, legal pages). A service without it is not a site.
++ * How the network presents the service as a site people visit (the navigation, the network home page, legal pages). A service without it is not a site. 1.1.0 (plan T11 lane D) adds the optional showcase fields copied from the OpenVibe.Sites catalog (tld, accent, description, pillars, faq, keywords, vision, highlight, launch, relationships; defined in registry.product@1): a site carrying tld is the catalog home of its domain (its publicOrigin host, or host), so that domain has no manifests/products file.
*/
site?: {
/**
@@@ -284,6 +284,31 @@ export interface ServiceManifest {
* Only while the manifest has no publicOrigin.
*/
host?: string;
++ /**
++ * The product's short name used everywhere (frame service names, /shared paths), e.g. food for openvibe.food. Not unique: a moved product keeps it on both addresses.
++ */
++ tld?: string;
++ /**
++ * Brand colour, lower-case hex.
++ */
++ accent?: string;
++ /**
++ * The catalog's description, verbatim.
++ */
++ description?: string;
++ pillars?: Pillars;
++ faq?: Faq;
++ /**
++ * Comma-separated search keywords, verbatim.
++ */
++ keywords?: string;
++ vision?: Vision;
++ highlight?: Highlight;
++ /**
++ * What a person still has to do or decide before the product launches. Absent once nothing blocks it.
++ */
++ launch?: string;
++ relationships?: Relationships;
};
capabilities: string[];
eventsProduced: string[];
@@@ -512,6 +537,44 @@ export interface ServiceManifest {
notes?: string;
}
++/** [email protected] (owner: network) */
++/**
++ * A product domain of the network's catalog (manifests/products/<domain>.json) that has no service manifest presenting it as a site: a planned product, a sub-domain of a service, a brand domain of a multi-domain service, a closed or moved address. Carries what the network's showcase pages need, copied from the OpenVibe.Sites catalog (plan T11 lane D) so Sites can be deleted. A product whose service manifest has a site block carries the same fields there (registry.service-manifest 1.1.0 site); every catalog domain has exactly one of the two homes. The $defs are shared with that site block.
++ */
++export interface Product {
++ /**
++ * The product's host, unique across the catalog.
++ */
++ domain: string;
++ tld: Tld;
++ /**
++ * Full name, e.g. OpenVibe.Food.
++ */
++ name: string;
++ /**
++ * Icon name of the showcase pages (a Font Awesome name such as fa-utensils).
++ */
++ icon: string;
++ accent: Accent;
++ tagline: string;
++ description: Description;
++ /**
++ * Which legal wording its /terms, /privacy and /dmca use (openvibe-shared/legal), as in a service manifest's site.
++ */
++ legalProfile?: "streaming" | "tools" | "ugc" | "games" | "hosting" | "account" | "info";
++ /**
++ * Absent for a product page. closed: the address was decided against and points elsewhere; moved: the product lives at another address; status: the address shows the network's status. relationships.links say where.
++ */
++ kind?: "closed" | "moved" | "status";
++ pillars?: Pillars;
++ faq?: Faq;
++ keywords: Keywords;
++ vision?: Vision;
++ highlight?: Highlight;
++ launch?: Launch;
++ relationships?: Relationships;
++}
++
/** [email protected] (owner: network) */
/**
* An action a principal may invoke. Authorization is checked at the owner's boundary against the grant, never inferred from the caller's route.
ddiff --git a/index.js b/index.js
iindex 5170c5b2..112954f5 100644
---- a/index.js
++++ b/index.js
@@@ -21,6 +21,7 @@ module.exports = {
http: require('./lib/http'),
capabilities: require('./lib/capabilities'),
services: require('./lib/services'),
++ products: require('./lib/products'),
serviceAuth: require('./lib/service-auth'),
modules: require('./lib/modules'),
tools: require('./lib/tools'),
ddiff --git a/lib/products.js b/lib/products.js
nnew file mode 100644
iindex 00000000..ed26a67b
---- /dev/null
++++ b/lib/products.js
@@@ -0,0 +1,26 @@
++'use strict';
++/**
++ * The network's product catalog (manifests/products/<domain>.json, contract registry.product@1): the
++ * product domains no service manifest presents as a site. Together with the service manifests whose
++ * site carries a tld, it is the catalog the OpenVibe.Sites repository used to hold (plan T11 lane D):
++ * `catalog()` lists both homes, one row per domain.
++ */
++const fs = require('fs');
++const path = require('path');
++const services = require('./services');
++const DIR = path.join(__dirname, '..', 'manifests', 'products');
++const manifests = fs.readdirSync(DIR).filter(f => f.endsWith('.json')).sort()
++ .map(f => JSON.parse(fs.readFileSync(path.join(DIR, f), 'utf8')));
++
++/** The host a service manifest's site is served on. */
++const siteDomain = (m) => (m.publicOrigin ? new URL(m.publicOrigin).host : m.site.host);
++
++/** Every catalog domain with its home: { domain, home: 'service' | 'product', id, entry }. */
++function catalog() {
++ const rows = services.manifests.filter(m => m.site && m.site.tld)
++ .map(m => ({ domain: siteDomain(m), home: 'service', id: m.id, entry: m.site }));
++ for (const p of manifests) rows.push({ domain: p.domain, home: 'product', id: p.domain, entry: p });
++ return rows.sort((a, b) => a.domain.localeCompare(b.domain));
++}
++
++module.exports = { manifests, get: (domain) => manifests.find(p => p.domain === domain), siteDomain, catalog };
ddiff --git a/manifests/products/admin.openvibe.network.json b/manifests/products/admin.openvibe.network.json
nnew file mode 100644
iindex 00000000..335f93c6
---- /dev/null
++++ b/manifests/products/admin.openvibe.network.json
@@@ -0,0 +1,40 @@
++{
++ "domain": "admin.openvibe.network",
++ "tld": "admin",
++ "name": "admin.openvibe.network",
++ "icon": "fa-shield-halved",
++ "accent": "#f59e0b",
++ "tagline": "One operator console for every service.",
++ "legalProfile": "account",
++ "description": "admin.openvibe.network will be the federated operator console: users, roles, bans, registry and policy, event queues, storage, billing, migrations and emergency controls, discovered from each service rather than hard-wired.",
++ "pillars": [
++ {
++ "title": "Users & roles",
++ "text": "Accounts, staff roles, restrictions and platform bans."
++ },
++ {
++ "title": "Registry & policy",
++ "text": "Service manifests, capability grants and namespaces."
++ },
++ {
++ "title": "Queues & storage",
++ "text": "Event delivery, dead letters, media diagnostics."
++ },
++ {
++ "title": "Audit",
++ "text": "Every privileged action recorded."
++ }
++ ],
++ "keywords": "openvibe admin, openvibe network admin, openvibe platform",
++ "relationships": {
++ "service": "network",
++ "plannedRepo": "OpenVibe.Network",
++ "meanwhile": [
++ {
++ "label": "Current admin on openvibe.network",
++ "url": "https://openvibe.network/admin",
++ "note": "Staff only. The present admin surface."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/ai.openvibe.network.json b/manifests/products/ai.openvibe.network.json
nnew file mode 100644
iindex 00000000..a7342205
---- /dev/null
++++ b/manifests/products/ai.openvibe.network.json
@@@ -0,0 +1,29 @@
++{
++ "domain": "ai.openvibe.network",
++ "tld": "ai",
++ "name": "ai.openvibe.network",
++ "icon": "fa-robot",
++ "accent": "#c084fc",
++ "tagline": "Moved: OpenVibe.AI's public home is ai.openvibe.services.",
++ "legalProfile": "info",
++ "kind": "moved",
++ "description": "OpenVibe.AI's public address is now ai.openvibe.services, next to the rest of the network's programmable services on OpenVibe.Services. This address serves only this notice until every client has moved; then it redirects.",
++ "keywords": "openvibe ai, openvibe network ai, openvibe platform",
++ "relationships": {
++ "service": "ai",
++ "plannedRepo": "OpenVibe.AI",
++ "linksLead": "OpenVibe.AI lives on OpenVibe.Services now.",
++ "links": [
++ {
++ "label": "ai.openvibe.services",
++ "url": "https://ai.openvibe.services/",
++ "note": "OpenVibe.AI: the gateway, templates, workflows and runs."
++ },
++ {
++ "label": "OpenVibe.Services",
++ "url": "https://openvibe.services/",
++ "note": "Every programmable OpenVibe service in one place."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/ai.openvibe.services.json b/manifests/products/ai.openvibe.services.json
nnew file mode 100644
iindex 00000000..81010e7a
---- /dev/null
++++ b/manifests/products/ai.openvibe.services.json
@@@ -0,0 +1,41 @@
++{
++ "domain": "ai.openvibe.services",
++ "tld": "ai",
++ "name": "ai.openvibe.services",
++ "icon": "fa-robot",
++ "accent": "#c084fc",
++ "tagline": "One AI gateway for every model you choose.",
++ "legalProfile": "info",
++ "description": "OpenVibe.AI is the network's AI gateway: OpenAI-compatible providers, Anthropic, local and open models behind one API, with routing for cost, speed, quality or privacy, provider fallback, versioned prompt templates and typed workflows, durable runs, structured output, embeddings, speech to text and media analysis, budgets and your own provider keys. It runs today as an internal service that OpenVibe.Live and OpenVibe.Network call; its public API and playground open here.",
++ "pillars": [
++ {
++ "title": "Any provider",
++ "text": "OpenAI-compatible, Anthropic, local llama.cpp and open models, one API."
++ },
++ {
++ "title": "Routing & fallback",
++ "text": "Cheap, fast, best or private: routes choose, and fall back when a provider fails."
++ },
++ {
++ "title": "Templates & workflows",
++ "text": "Versioned prompts and typed workflows with durable, resumable runs."
++ },
++ {
++ "title": "Your keys, your budget",
++ "text": "Bring your own provider keys; quotas and budgets per project."
++ }
++ ],
++ "keywords": "ai gateway, open source ai api, llm router, model routing, prompt templates, ai workflows, speech to text api, openvibe ai",
++ "launch": "a public API with its own keys, quotas and playground; today only other OpenVibe services call it",
++ "relationships": {
++ "service": "ai",
++ "plannedRepo": "OpenVibe.AI",
++ "meanwhile": [
++ {
++ "label": "AI on OpenVibe.Live",
++ "url": "https://openvibe.live",
++ "note": "Live's AI moments, recaps, summaries and translations run through OpenVibe.AI; captions still run inside Live."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/api.openvibe.network.json b/manifests/products/api.openvibe.network.json
nnew file mode 100644
iindex 00000000..05b4117a
---- /dev/null
++++ b/manifests/products/api.openvibe.network.json
@@@ -0,0 +1,50 @@
++{
++ "domain": "api.openvibe.network",
++ "tld": "api",
++ "name": "api.openvibe.network",
++ "icon": "fa-plug",
++ "accent": "#22d3ee",
++ "tagline": "The network, machine-readable.",
++ "legalProfile": "info",
++ "description": "api.openvibe.network is the planned home of the network's public APIs. The ecosystem registry (services, capabilities, event topics, namespaces and releases, backed by the OpenVibe contracts) already answers on openvibe.network; this subdomain is not routed to it yet.",
++ "pillars": [
++ {
++ "title": "Service registry",
++ "text": "Every service, its domains, health and maturity, in one catalog."
++ },
++ {
++ "title": "Capabilities",
++ "text": "Action-level permissions apps and mods can request and be granted."
++ },
++ {
++ "title": "Event topics",
++ "text": "Durable event families and their schemas."
++ },
++ {
++ "title": "Releases",
++ "text": "Release manifests and compatibility ranges per service."
++ }
++ ],
++ "keywords": "openvibe api, openvibe network api, openvibe platform",
++ "relationships": {
++ "service": "network",
++ "plannedRepo": "OpenVibe.Network",
++ "meanwhile": [
++ {
++ "label": "Registry API",
++ "url": "https://openvibe.network/api/v1/registry",
++ "note": "Services, capabilities, namespaces, event topics and releases, as JSON."
++ },
++ {
++ "label": "Service discovery",
++ "url": "https://openvibe.network/.well-known/openvibe",
++ "note": "The issuer, token and key endpoints, and where each service answers."
++ },
++ {
++ "label": "Developer portal",
++ "url": "https://openvibe.codes",
++ "note": "Projects, apps and credentials, contract and SDK docs on OpenVibe.Codes."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/auth.openvibe.network.json b/manifests/products/auth.openvibe.network.json
nnew file mode 100644
iindex 00000000..42cabb94
---- /dev/null
++++ b/manifests/products/auth.openvibe.network.json
@@@ -0,0 +1,45 @@
++{
++ "domain": "auth.openvibe.network",
++ "tld": "auth",
++ "name": "auth.openvibe.network",
++ "icon": "fa-key",
++ "accent": "#60a5fa",
++ "tagline": "Sign in, once, for the whole network.",
++ "legalProfile": "account",
++ "description": "auth.openvibe.network will be the dedicated home of OpenVibe identity: sign-in, registration, OAuth 2.0 / OpenID Connect for apps, sessions and linked accounts. Today all of this runs inside openvibe.network itself.",
++ "pillars": [
++ {
++ "title": "OAuth 2.0 + PKCE",
++ "text": "Authorization-code flow with PKCE for apps and mods."
++ },
++ {
++ "title": "OpenID Connect",
++ "text": "Discovery, JWKS and standard ID tokens for any relying party."
++ },
++ {
++ "title": "Sessions & devices",
++ "text": "See and revoke every session from one place."
++ },
++ {
++ "title": "Linked accounts",
++ "text": "Steam, Discord, GitHub and more, tied to one OpenVibe identity."
++ }
++ ],
++ "keywords": "openvibe auth, openvibe network auth, openvibe platform",
++ "relationships": {
++ "service": "network",
++ "plannedRepo": "OpenVibe.Network",
++ "meanwhile": [
++ {
++ "label": "Sign in on openvibe.network",
++ "url": "https://openvibe.network/auth/login",
++ "note": "The live sign-in flow, used by every OpenVibe site today."
++ },
++ {
++ "label": "OIDC discovery",
++ "url": "https://openvibe.network/oauth/.well-known/openid-configuration",
++ "note": "The OpenID configuration Network publishes today."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.actor.json b/manifests/products/openvibe.actor.json
nnew file mode 100644
iindex 00000000..eb057564
---- /dev/null
++++ b/manifests/products/openvibe.actor.json
@@@ -0,0 +1,393 @@
++{
++ "domain": "openvibe.actor",
++ "tld": "actor",
++ "name": "OpenVibe.Actor",
++ "icon": "fa-user-astronaut",
++ "accent": "#a78bfa",
++ "tagline": "Every AI agent, every device, one command console.",
++ "description": "OpenVibe.Actor sends each task to the best agent for it, across hosted agent platforms, open-source runtimes, coding agents and OpenVibe's own, balanced live on price, speed and success. Install the OpenVibe Node on your computers, servers, Raspberry Pis and robots, and your agents can work on them for real, at the control level you choose, with a kill switch you hold. Run everything from one console, or host the whole thing yourself.",
++ "pillars": [
++ {
++ "title": "Your agents",
++ "text": "Personas, memory, tools, schedules and budgets you control."
++ },
++ {
++ "title": "Permission first",
++ "text": "Delegated grants and approvals, never an all-powerful account."
++ },
++ {
++ "title": "Any model",
++ "text": "Routed through OpenVibe.AI: hosted, open or local models."
++ },
++ {
++ "title": "Works with everything",
++ "text": "Watch, Website, Work, Bot, Events, Tools and more."
++ }
++ ],
++ "faq": [
++ {
++ "question": "Is this like OpenRouter?",
++ "answer": "Yes, for agents: one API and one bill for many agent systems, with live prices and rankings. Actor routes whole tasks rather than single model calls, and it checks the results before you get them."
++ },
++ {
++ "question": "Which agent systems does it use?",
++ "answer": "OpenVibe's own runtime, hosted agent platforms from companies like OpenAI and Anthropic, coding agents, and open-source agents on OpenVibe's hardware. Each is an adapter, so new ones join quickly."
++ },
++ {
++ "question": "How does it choose?",
++ "answer": "For each kind of task it compares price, speed, success rate and current health across agent systems, then applies your mode and budget. Every choice is recorded and explained."
++ },
++ {
++ "question": "Can I keep my data on OpenVibe?",
++ "answer": "Yes. Private mode uses only OpenVibe's own runtime and open models on its own hardware."
++ },
++ {
++ "question": "Can it act without asking?",
++ "answer": "Only as far as you allow. Anything with consequences, like publishing, spending or moving a robot, waits for your approval, and every action is logged."
++ },
++ {
++ "question": "Can an agent take full control of my computer?",
++ "answer": "Only if you set that Node to Full control on the device itself. It is time-boxed and recorded, you can see when an agent is in control, and a button, a hotkey or one command ends it. Most people use Trusted: routine actions run and risky ones ask first."
++ },
++ {
++ "question": "Can I run Actor myself?",
++ "answer": "Yes. The router, the runtime, the console and the Node are open source and run on your own server with local models, and can link to openvibe.actor when you want hosted agents or the rest of the network."
++ },
++ {
++ "question": "What will it cost?",
++ "answer": "You will be able to start without paying, within limits published before launch; bigger tasks and stronger agents will be paid, with the cost shown per task."
++ }
++ ],
++ "keywords": "ai agent router, openrouter for agents, ai agent that controls your computer, remote control computer with ai, self-hosted ai agent, computer use agent, ai command console, agent api, cloud ai agent, mcp server",
++ "vision": {
++ "badge": "being built",
++ "cta": [
++ "See the network",
++ "https://openvibe.network/#network",
++ "fa-circle-nodes"
++ ],
++ "viz": {
++ "kind": "router",
++ "budget": "budget 50¢",
++ "task": "Research five competitors of my shop and write a one-page brief with sources.",
++ "modes": [
++ "Cheapest",
++ "Balanced",
++ "Best",
++ "Fastest",
++ "Private"
++ ],
++ "mode": "Balanced",
++ "candidates": [
++ {
++ "icon": "fa-circle-nodes",
++ "name": "OpenVibe runtime",
++ "cost": "6¢",
++ "time": "3 min",
++ "success": "92%",
++ "pick": true
++ },
++ {
++ "icon": "fa-cloud",
++ "name": "Hosted frontier agent",
++ "cost": "31¢",
++ "time": "2 min",
++ "success": "95%"
++ },
++ {
++ "icon": "fa-code",
++ "name": "Coding agent",
++ "cost": "12¢",
++ "time": "4 min",
++ "success": "81%"
++ },
++ {
++ "icon": "fa-lock",
++ "name": "Open model on OpenVibe",
++ "cost": "1¢",
++ "time": "7 min",
++ "success": "74%"
++ }
++ ],
++ "why": "Meets this task's quality bar at the lowest expected cost; a second model family checks the brief before you get it.",
++ "caption": "Design preview; the numbers are illustrative",
++ "label": "Design preview: a task routed across four agent systems with cost, time and success estimates, the balanced mode selected"
++ },
++ "stepsTitle": "How a task runs",
++ "stepsLead": "Send a task from the site, the API or any OpenVibe app. Actor does the rest.",
++ "steps": [
++ [
++ "Describe the task",
++ "Say what you want done, pick a mode and set a budget. Attach files or give it a robot, a site or a stream to work with."
++ ],
++ [
++ "Actor picks the agent",
++ "It weighs every agent system on live price, speed and success for this kind of task, picks one, and tells you why."
++ ],
++ [
++ "Checked, then delivered",
++ "A different model checks the result; a failed check moves the task to a stronger agent. You see the cost to the cent."
++ ]
++ ],
++ "featuresTitle": "Modes",
++ "featuresLead": "Tell Actor what matters most. It balances the rest.",
++ "features": [
++ [
++ "fa-coins",
++ "Cheapest",
++ "The lowest expected cost that still meets the task's quality bar."
++ ],
++ [
++ "fa-scale-balanced",
++ "Balanced",
++ "The default: cost, quality and speed weighed together."
++ ],
++ [
++ "fa-trophy",
++ "Best",
++ "The highest chance of a right answer first time; cost comes second."
++ ],
++ [
++ "fa-bolt",
++ "Fastest",
++ "The shortest time to a checked result."
++ ],
++ [
++ "fa-lock",
++ "Private",
++ "Only agents that keep your data on OpenVibe: its own runtime and open models on its hardware."
++ ],
++ [
++ "fa-wallet",
++ "Budgets",
++ "A hard limit per task and per day. Actor never goes over it."
++ ]
++ ],
++ "more": [
++ {
++ "id": "routes",
++ "title": "What it routes to",
++ "lead": "Every agent system worth using, behind one API, one key and one bill.",
++ "cards": [
++ [
++ "fa-circle-nodes",
++ "OpenVibe's own runtime",
++ "Uses every OpenVibe product as a tool, with models chosen by OpenVibe.AI."
++ ],
++ [
++ "fa-cloud",
++ "Hosted agent platforms",
++ "Agent platforms from companies like OpenAI and Anthropic, with their browsers, code runners and search."
++ ],
++ [
++ "fa-code",
++ "Coding agents",
++ "For tasks that are really code: fixes, features, reviews and tests."
++ ],
++ [
++ "fa-server",
++ "Open-source agents",
++ "Open runtimes and open models on OpenVibe's own hardware: the Private mode."
++ ],
++ [
++ "fa-key",
++ "Your own keys",
++ "Bring a key for any platform and pay it directly; Actor still routes, checks and explains."
++ ],
++ [
++ "fa-plug-circle-plus",
++ "New ones, fast",
++ "A new agent platform is one adapter. The day it lands, every app and agent can use it."
++ ]
++ ]
++ },
++ {
++ "id": "devices",
++ "title": "Your devices, under control",
++ "lead": "Install the OpenVibe Node on a computer, a server, a Raspberry Pi or a phone. No open ports: it connects out. You decide how much an agent may do.",
++ "cards": [
++ [
++ "fa-terminal",
++ "Terminal and files",
++ "A live terminal, a file browser, processes and services, from anywhere, for you and your agents."
++ ],
++ [
++ "fa-display",
++ "Screen, keyboard and mouse",
++ "Agents can use any app on the machine the way you would, and you watch the screen while they work."
++ ],
++ [
++ "fa-code-branch",
++ "Your own coding agents",
++ "The coding agents already installed on the machine become agents Actor can hand tasks to."
++ ],
++ [
++ "fa-sliders",
++ "Four control levels",
++ "Observe, Ask, Trusted or Full control, set on the device itself. Full control is time-boxed and recorded."
++ ],
++ [
++ "fa-power-off",
++ "A kill switch you hold",
++ "A button, a hotkey or one command on the device ends any agent session at once. Work can stay inside a container."
++ ],
++ [
++ "fa-robot",
++ "Robots too",
++ "The same Node drives robots on OpenVibe.Bot, so one install covers your computers and your robots.",
++ "https://openvibe.bot"
++ ]
++ ]
++ },
++ {
++ "id": "console",
++ "title": "One console for everything",
++ "lead": "Agents, tasks, approvals, devices, robots, sites, streams and costs in one place, on the web and on your phone.",
++ "cards": [
++ [
++ "fa-keyboard",
++ "Command bar",
++ "Type what you want in plain words or as a slash command; it goes to the right agent, device or product."
++ ],
++ [
++ "fa-list-check",
++ "Tasks and approvals",
++ "Live progress for every task, and one inbox for everything waiting on you."
++ ],
++ [
++ "fa-server",
++ "Devices",
++ "Every Node with its status, a terminal, its files and its screen."
++ ],
++ [
++ "fa-robot",
++ "Robots",
++ "Your robots' control panels from OpenVibe.Bot, next to everything else.",
++ "https://openvibe.bot"
++ ],
++ [
++ "fa-tower-broadcast",
++ "Sites and streams",
++ "Deploys on OpenVibe.Host and your streams on OpenVibe.Live and openre.stream."
++ ],
++ [
++ "fa-wave-square",
++ "Events and costs",
++ "What happened across the network, live, and what every task cost."
++ ]
++ ]
++ },
++ {
++ "title": "Balanced live, explained always",
++ "lead": "Choices come from what actually happens, not from marketing pages.",
++ "cards": [
++ [
++ "fa-chart-line",
++ "Live signals",
++ "Price, speed, success rate and outages per agent system and per kind of task, updated from real runs."
++ ],
++ [
++ "fa-arrows-rotate",
++ "Failover",
++ "When an agent system slows down or fails, tasks move to the next best one at once."
++ ],
++ [
++ "fa-list-check",
++ "Checked results",
++ "Cheap first, then stronger agents only when a check fails: you pay for strength only when a task needs it."
++ ],
++ [
++ "fa-magnifying-glass-chart",
++ "Public rankings",
++ "Success, cost and time per agent system for each kind of task, from real traffic, with dates.",
++ null
++ ],
++ [
++ "fa-receipt",
++ "Every choice explained",
++ "Which agent ran, why it won, what else was considered and what it cost."
++ ],
++ [
++ "fa-user-astronaut",
++ "Your agents, on top",
++ "Always-on agents with memory, schedules and approvals; every task they take goes through the router."
++ ]
++ ]
++ }
++ ],
++ "templates": {
++ "title": "Start an agent from a template",
++ "lead": "Each one is a starting point you can change, and each routes its work through Actor.",
++ "items": [
++ [
++ "fa-tower-broadcast",
++ "Stream co-host"
++ ],
++ [
++ "fa-shield",
++ "Chat moderator"
++ ],
++ [
++ "fa-magnifying-glass",
++ "Research assistant"
++ ],
++ [
++ "fa-globe",
++ "Site builder"
++ ],
++ [
++ "fa-robot",
++ "Robot pilot"
++ ],
++ [
++ "fa-calendar-check",
++ "Personal assistant"
++ ]
++ ]
++ },
++ "connectTitle": "Reachable everywhere",
++ "connectLead": "Actor is part of the network, not a separate app.",
++ "connect": [
++ [
++ "fa-bars",
++ "On every OpenVibe site",
++ "An agent button in the navbar, with the page you are on as context."
++ ],
++ [
++ "fa-comments",
++ "In chat",
++ "Direct messages on OpenVibe.Chat and @mentions in Live chat and Community threads.",
++ "https://openvibe.chat"
++ ],
++ [
++ "fa-robot",
++ "With your robots",
++ "Give an agent a robot on OpenVibe.Bot to patrol, check on or perform with, with your approval.",
++ "https://openvibe.bot"
++ ],
++ [
++ "fa-plug",
++ "From your own apps",
++ "An API, webhooks and an MCP server, so other assistants can hand tasks to Actor.",
++ "https://openvibe.codes"
++ ]
++ ],
++ "building": "OpenVibe.Actor is being built now, on the network's accounts, AI router and event system that already run."
++ },
++ "launch": "the task router with its first agent adapters, then personal agents on OpenVibe.Network accounts",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Tools",
++ "url": "https://openvibe.tools",
++ "note": "Tools agents will call, usable by people today."
++ },
++ {
++ "label": "OpenVibe.Codes",
++ "url": "https://openvibe.codes",
++ "note": "The API and SDK agents are built on."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.bot.json b/manifests/products/openvibe.bot.json
nnew file mode 100644
iindex 00000000..4217e45e
---- /dev/null
++++ b/manifests/products/openvibe.bot.json
@@@ -0,0 +1,220 @@
++{
++ "domain": "openvibe.bot",
++ "tld": "bot",
++ "name": "OpenVibe.Bot",
++ "icon": "fa-robot",
++ "accent": "#f472b6",
++ "tagline": "Get your robot online in minutes.",
++ "description": "OpenVibe.Bot is an open control panel for robots. Add a robot, pair it with one command or a QR code, and drive it from any browser with live video, real controls and a stop button that always works. Buy a ready-made bot or bring your own: a Raspberry Pi robot, an ESP32, a ROS 2 robot, a phone, or a Cozmo. Share control, automate it, hand it to your AI agent, and stream it to OpenVibe.Live or openre.stream when you want to.",
++ "pillars": [
++ {
++ "title": "Pair devices",
++ "text": "Revocable device keys and one-time pairing codes, not stream keys."
++ },
++ {
++ "title": "Share control",
++ "text": "Owner, whitelist, queues, timed turns, moderators and apps."
++ },
++ {
++ "title": "Safe on the device",
++ "text": "Dead-man stop, command limits and local emergency stop."
++ },
++ {
++ "title": "Stream it",
++ "text": "Connect a device to a Live stream, a game or a website."
++ }
++ ],
++ "faq": [
++ {
++ "question": "Do I need to open ports on my router?",
++ "answer": "No. The robot connects out to OpenVibe, and video and controls travel over WebRTC, so it works behind ordinary home and mobile networks."
++ },
++ {
++ "question": "Is it only for streaming?",
++ "answer": "No. Streaming is optional. Bot is a control panel for your robot first: drive it, check on it, share it and automate it."
++ },
++ {
++ "question": "Which robot comes first?",
++ "answer": "The Adeept 4WD Smart Car Kit for Raspberry Pi: four motors, a pan-tilt camera, distance and line sensors, lights and a buzzer. OpenVibe's device agent drives it directly, so its own open control port never has to be exposed."
++ },
++ {
++ "question": "Does it work with Cozmo?",
++ "answer": "Yes, built in. Cozmo makes its own Wi-Fi and runs closed firmware, so the OpenVibe agent runs on a computer next to it (a Raspberry Pi or a laptop) that joins Cozmo's Wi-Fi and stays online through another connection. You get driving, the head and lift, its camera, its face display, its voice, lights and cubes, and it stops at edges on its own. No phone app needed."
++ },
++ {
++ "question": "What hardware works?",
++ "answer": "A Raspberry Pi or another Linux board, an ESP32, a ROS 2 robot, an Android phone, or an ONVIF or RTSP camera. The curated kits list exactly what to buy."
++ },
++ {
++ "question": "How does it keep the robot safe?",
++ "answer": "The device stops its motors by itself when commands or the connection stop. There is an emergency stop in the panel, limits on speed, range and time, and every command is logged."
++ },
++ {
++ "question": "What will it cost?",
++ "answer": "Controlling your own robot will not cost anything. Limits and any paid extras will be published before launch."
++ }
++ ],
++ "keywords": "robot control panel, control a robot from the browser, remote control robot online, raspberry pi robot web control, esp32 robot control, ros 2 web control, telepresence robot, stream a robot live, open source robot controller, cozmo remote control, cozmo web control, cozmo sdk alternative",
++ "vision": {
++ "badge": "being built",
++ "cta": [
++ "Robots on Live today",
++ "https://openvibe.live",
++ "fa-tower-broadcast"
++ ],
++ "viz": {
++ "kind": "panel",
++ "name": "Rover-1",
++ "latency": "84 ms",
++ "onair": "Streaming to openvibe.live",
++ "battery": 82,
++ "keys": [
++ [
++ "fa-lightbulb",
++ "Lights"
++ ],
++ [
++ "fa-bullhorn",
++ "Horn"
++ ],
++ [
++ "fa-comment",
++ "Speak"
++ ],
++ [
++ "fa-camera-rotate",
++ "Camera"
++ ]
++ ],
++ "caption": "Design preview of the robot panel",
++ "label": "Design preview: a robot control panel with live video, a drive pad, buttons, battery level and a stop button"
++ },
++ "stepsTitle": "Online in three steps",
++ "stepsLead": "No port forwarding, no stream keys, no config files.",
++ "steps": [
++ [
++ "Add a robot",
++ "Name it and pick what it is: a rover, an arm, a camera, a phone on wheels or your own design."
++ ],
++ [
++ "Pair it",
++ "Paste one command on the robot, flash a ready-made card, or scan a QR code. It gets its own key, which you can revoke any time."
++ ],
++ [
++ "Drive it",
++ "Live video and controls in any browser, phone included, with a stop button that always works."
++ ]
++ ],
++ "choices": {
++ "title": "Buy one or bring your own",
++ "lead": "Start with hardware that is known to work, or use what you already have.",
++ "items": [
++ {
++ "icon": "fa-box-open",
++ "title": "Get a robot",
++ "text": "Curated kits with parts lists, tested settings and build guides, and ready-made bots that arrive paired to your account. The first one on OpenVibe.Bot is the Adeept 4WD Smart Car for Raspberry Pi.",
++ "chips": [
++ "Adeept 4WD Smart Car (Raspberry Pi)",
++ "Rover kits",
++ "Telepresence bots",
++ "Pan-tilt cameras",
++ "Robot arms"
++ ]
++ },
++ {
++ "icon": "fa-screwdriver-wrench",
++ "title": "Bring your own",
++ "text": "Anything with motors, a camera or sensors. Install the OpenVibe device agent on the robot, or, for robots with their own firmware like Cozmo, on a computer next to it, and your robot shows up in the panel.",
++ "chips": [
++ "Raspberry Pi and Linux boards",
++ "Cozmo, through a bridge",
++ "ESP32",
++ "ROS 2",
++ "Android phones",
++ "ONVIF and RTSP cameras",
++ "Serial motor boards"
++ ]
++ }
++ ]
++ },
++ "featuresTitle": "More than a stream",
++ "featuresLead": "A control panel for anything you build, whether or not anyone is watching.",
++ "features": [
++ [
++ "fa-gamepad",
++ "Real controls",
++ "Drive pad, keyboard, touch joystick and gamepad; buttons, sliders, servos and camera pan-tilt, laid out from your robot's profile."
++ ],
++ [
++ "fa-bolt",
++ "Low latency",
++ "Video and commands over WebRTC, so driving feels direct. Works behind ordinary home and mobile networks."
++ ],
++ [
++ "fa-user-group",
++ "Share control",
++ "Invite friends as operators, or open a public queue with timed turns, cooldowns and per-role limits."
++ ],
++ [
++ "fa-shield-halved",
++ "Safe by default",
++ "The robot stops itself when commands stop. An emergency stop, speed and range limits and time windows, all logged."
++ ],
++ [
++ "fa-diagram-project",
++ "Automate it",
++ "Schedules, triggers and webhooks, plus an API and SDKs for your own apps."
++ ],
++ [
++ "fa-user-astronaut",
++ "Hand it to your agent",
++ "Let your OpenVibe.Actor agent patrol, check on or perform with your robot, with your approval."
++ ]
++ ],
++ "connectTitle": "Stream it, record it, connect it",
++ "connectLead": "Optional, one toggle each. Your robot does not need an audience to be useful.",
++ "connect": [
++ [
++ "fa-tower-broadcast",
++ "Stream to OpenVibe.Live",
++ "Put the robot's camera on your channel; viewers can drive it from chat or the panel.",
++ "https://openvibe.live"
++ ],
++ [
++ "fa-satellite-dish",
++ "Stream through openre.stream",
++ "Low-latency ingest and restreaming to other platforms from the same camera.",
++ "https://openre.stream"
++ ],
++ [
++ "fa-photo-film",
++ "Record to OpenVibe.Media",
++ "Keep every run as a video and cut clips from it.",
++ "https://openvibe.media"
++ ],
++ [
++ "fa-code",
++ "Build on it",
++ "The API, SDKs and examples.",
++ "https://openvibe.codes"
++ ]
++ ],
++ "building": "OpenVibe.Bot is being built now, starting with our own robots; stream-controlled robots already run on OpenVibe.Live."
++ },
++ "launch": "the Bot service, its device agent and the first robots paired",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "Robots on OpenVibe.Live",
++ "url": "https://openvibe.live",
++ "note": "Stream-controlled robots and cameras run on Live today."
++ },
++ {
++ "label": "OpenVibe.Extensions",
++ "url": "https://github.com/OpenVibers/OpenVibe.Extensions",
++ "note": "Device-side agents and hardware scripts."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.download.json b/manifests/products/openvibe.download.json
nnew file mode 100644
iindex 00000000..9818a810
---- /dev/null
++++ b/manifests/products/openvibe.download.json
@@@ -0,0 +1,45 @@
++{
++ "domain": "openvibe.download",
++ "tld": "download",
++ "name": "OpenVibe.Download",
++ "icon": "fa-cloud-arrow-down",
++ "accent": "#34d399",
++ "tagline": "Upload anything. Share it anywhere.",
++ "description": "OpenVibe.Download will be the file home of the OpenVibe network: large uploads and simple share links with or without an account, folders, sync and backups when you sign in, and AI that previews, sorts and finds your files, in the spirit of MediaFire and MEGA, built on OpenVibe.Media and open through the API.",
++ "pillars": [
++ {
++ "title": "Share links",
++ "text": "Big files, simple links, clear limits."
++ },
++ {
++ "title": "Drive & backup",
++ "text": "Folders, sync and backups under your OpenVibe account."
++ },
++ {
++ "title": "Smart files",
++ "text": "Previews, search and summaries from the network's AI."
++ },
++ {
++ "title": "Built for apps",
++ "text": "The same file service every OpenVibe site and your own apps use."
++ }
++ ],
++ "keywords": "openvibe download, file sharing, file upload, cloud storage, backup, mediafire alternative",
++ "launch": "the shared file service on OpenVibe.Media",
++ "relationships": {
++ "service": "media-hub",
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Media",
++ "url": "https://openvibe.media",
++ "note": "Media already stores the network's uploads."
++ },
++ {
++ "label": "Document tools",
++ "url": "https://docs.openvibe.tools",
++ "note": "Convert and work with documents today."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.events.json b/manifests/products/openvibe.events.json
nnew file mode 100644
iindex 00000000..60b659c0
---- /dev/null
++++ b/manifests/products/openvibe.events.json
@@@ -0,0 +1,40 @@
++{
++ "domain": "openvibe.events",
++ "tld": "events",
++ "name": "OpenVibe.Events",
++ "icon": "fa-bolt",
++ "accent": "#34d399",
++ "tagline": "Events, queues and webhooks you can build on.",
++ "description": "OpenVibe.Events is the network's event system: durable events, topics and subscriptions, signed webhooks with retries, dead-letter queues and replay, pull consumers with checkpoints, and realtime delivery to browsers with cursor resume. Every OpenVibe product already publishes through it. This domain becomes its home, with queues, streams, project namespaces and developer tools.",
++ "pillars": [
++ {
++ "title": "Topics & webhooks",
++ "text": "Fan out to subscribers with signed, retried deliveries."
++ },
++ {
++ "title": "Queues & streams",
++ "text": "Competing consumers with leases, and ordered history with cursors."
++ },
++ {
++ "title": "Realtime",
++ "text": "Server-sent events to browsers, resuming where they left off."
++ },
++ {
++ "title": "Replay & DLQ",
++ "text": "Nothing is lost quietly: replay it, or inspect what failed."
++ }
++ ],
++ "keywords": "event bus api, webhook retry service, sqs alternative, sns alternative, pub sub api, realtime sse api, dead letter queue, webhook replay",
++ "launch": "the developer site and queue and stream primitives; the API runs at events.openvibe.network",
++ "relationships": {
++ "service": "events",
++ "plannedRepo": "OpenVibe.Events",
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Codes",
++ "url": "https://openvibe.codes",
++ "note": "Events API and SDK reference."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.food.json b/manifests/products/openvibe.food.json
nnew file mode 100644
iindex 00000000..92d95c35
---- /dev/null
++++ b/manifests/products/openvibe.food.json
@@@ -0,0 +1,44 @@
++{
++ "domain": "openvibe.food",
++ "tld": "food",
++ "name": "OpenVibe.Food",
++ "icon": "fa-utensils",
++ "accent": "#fb923c",
++ "tagline": "What should we eat?",
++ "description": "OpenVibe.Food will bring food together: compare delivery prices across apps, find coupons and budget meal kits, plan meals alone, as a couple or as a group, and turn what is in your cupboard into recipes with the network's AI. The food tools on OpenVibe.Tools will live here too.",
++ "pillars": [
++ {
++ "title": "Compare delivery",
++ "text": "One search across delivery apps, with prices side by side."
++ },
++ {
++ "title": "Kits & boxes",
++ "text": "Meal kits and snack boxes, budget-friendly picks first."
++ },
++ {
++ "title": "Plan together",
++ "text": "Shared plans and chats for couples, households and groups."
++ },
++ {
++ "title": "Cupboard recipes",
++ "text": "Tell it what you have; get ideas that use it."
++ }
++ ],
++ "keywords": "openvibe food, compare food delivery prices, meal kits, meal planning, recipe ideas, grocery tools",
++ "launch": "the food tools moving over from OpenVibe.Tools",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "Food tools",
++ "url": "https://food.openvibe.tools",
++ "note": "The food and grocery tools work today."
++ },
++ {
++ "label": "OpenVibe.Coupons",
++ "url": "https://openvibe.coupons",
++ "note": "Coupons are the sibling room."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.help.json b/manifests/products/openvibe.help.json
nnew file mode 100644
iindex 00000000..c8418ca0
---- /dev/null
++++ b/manifests/products/openvibe.help.json
@@@ -0,0 +1,44 @@
++{
++ "domain": "openvibe.help",
++ "tld": "help",
++ "name": "OpenVibe.Help",
++ "icon": "fa-life-ring",
++ "accent": "#4ade80",
++ "tagline": "Answers, support and a way to reach people.",
++ "description": "OpenVibe.Help will be the help and support center for every OpenVibe site: search answers drawn from the docs, wiki, blog and service status, follow troubleshooting guides, and open a ticket that stays one conversation on the web and by email. A Report Problem button on every site will attach the safe details staff need (release, request and trace ids) and never secrets.",
++ "pillars": [
++ {
++ "title": "Grounded answers",
++ "text": "From the real docs, wiki and status, with sources."
++ },
++ {
++ "title": "One support inbox",
++ "text": "Signed in or not, on the web or by email."
++ },
++ {
++ "title": "Report a problem",
++ "text": "Useful, privacy-scrubbed details attached for you."
++ },
++ {
++ "title": "For your projects",
++ "text": "A support widget and help center for sites built on OpenVibe."
++ }
++ ],
++ "keywords": "openvibe help, openvibe support, help center, support tickets, troubleshooting",
++ "launch": "the ticket service and the Frame's Help button",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "Network status",
++ "url": "https://openvibe.network/status",
++ "note": "Service health and incidents today."
++ },
++ {
++ "label": "OpenVibe.Wiki",
++ "url": "https://openvibe.wiki",
++ "note": "Guides and concepts."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.homes.json b/manifests/products/openvibe.homes.json
nnew file mode 100644
iindex 00000000..4884d3b3
---- /dev/null
++++ b/manifests/products/openvibe.homes.json
@@@ -0,0 +1,39 @@
++{
++ "domain": "openvibe.homes",
++ "tld": "homes",
++ "name": "OpenVibe.Homes",
++ "icon": "fa-house",
++ "accent": "#a3e635",
++ "tagline": "Homes to own, clearly explained.",
++ "description": "OpenVibe.Homes will be the owning side of OpenVibe.Rent: homes for sale, property records and neighbourhood context in one place, with alerts and AI summaries that always link their sources.",
++ "pillars": [
++ {
++ "title": "Listings",
++ "text": "Homes for sale from many sources, in one search."
++ },
++ {
++ "title": "Property records",
++ "text": "Sale history and public records, with their sources."
++ },
++ {
++ "title": "Neighbourhoods",
++ "text": "Context that helps you decide."
++ },
++ {
++ "title": "Alerts",
++ "text": "Be told when a home that fits appears."
++ }
++ ],
++ "keywords": "openvibe homes, homes for sale, property records, real estate search, home alerts",
++ "launch": "verified listing and records sources",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Deals",
++ "url": "https://openvibe.deals",
++ "note": "Deal discovery is the sibling room."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.pics.json b/manifests/products/openvibe.pics.json
nnew file mode 100644
iindex 00000000..d12ef44d
---- /dev/null
++++ b/manifests/products/openvibe.pics.json
@@@ -0,0 +1,45 @@
++{
++ "domain": "openvibe.pics",
++ "tld": "pics",
++ "name": "OpenVibe.Pics",
++ "icon": "fa-images",
++ "accent": "#f472b6",
++ "tagline": "Share pictures. Fast, open, yours.",
++ "description": "OpenVibe.Pics will be the picture home of the OpenVibe network: quick uploads and share links with or without an account, albums and galleries, image processing and optimisation served from a shared CDN, and AI image tools, an open and modular alternative to Imgur that is also usable through the OpenVibe API.",
++ "pillars": [
++ {
++ "title": "Upload & share",
++ "text": "Drop a picture, get a link, with or without an account."
++ },
++ {
++ "title": "Albums & galleries",
++ "text": "Collections, embeds and community galleries."
++ },
++ {
++ "title": "Processing",
++ "text": "Resize, convert and optimise on the fly, cached close to viewers."
++ },
++ {
++ "title": "AI image tools",
++ "text": "Describe, tag, edit and generate with the network's shared AI."
++ }
++ ],
++ "keywords": "openvibe pics, image hosting, image upload, imgur alternative, image cdn, ai image tools",
++ "launch": "the shared image upload and processing service",
++ "relationships": {
++ "service": "media-hub",
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Media",
++ "url": "https://openvibe.media",
++ "note": "Media already stores and serves the network's pictures and video."
++ },
++ {
++ "label": "Image tools",
++ "url": "https://img.openvibe.tools",
++ "note": "Convert, resize and compress images today."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.quest.json b/manifests/products/openvibe.quest.json
nnew file mode 100644
iindex 00000000..b771a15e
---- /dev/null
++++ b/manifests/products/openvibe.quest.json
@@@ -0,0 +1,39 @@
++{
++ "domain": "openvibe.quest",
++ "tld": "quest",
++ "name": "OpenVibe.Quest",
++ "icon": "fa-map",
++ "accent": "#fbbf24",
++ "tagline": "Quests across the whole network.",
++ "description": "OpenVibe.Quest will be the network's shared quest and rewards system: quests that span every OpenVibe site, from a streamer's getting-started steps on OpenVibe.Live to community challenges, cosmetics and OpenCoins, moddable and open to anyone through the API.",
++ "pillars": [
++ {
++ "title": "Quests everywhere",
++ "text": "One quest log across every OpenVibe site."
++ },
++ {
++ "title": "Rewards",
++ "text": "Cosmetics, badges and OpenCoins."
++ },
++ {
++ "title": "Make your own",
++ "text": "Streamers, modders and developers create quests for their communities."
++ },
++ {
++ "title": "Built for games",
++ "text": "The quest layer for OpenVibe.Games and its creators."
++ }
++ ],
++ "keywords": "openvibe quest, quests, achievements, rewards, gamification, streamer quests",
++ "launch": "the shared quest service",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Live",
++ "url": "https://openvibe.live",
++ "note": "Streamers' getting-started steps are on OpenVibe.Live today."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.rent.json b/manifests/products/openvibe.rent.json
nnew file mode 100644
iindex 00000000..474b1604
---- /dev/null
++++ b/manifests/products/openvibe.rent.json
@@@ -0,0 +1,39 @@
++{
++ "domain": "openvibe.rent",
++ "tld": "rent",
++ "name": "OpenVibe.Rent",
++ "icon": "fa-key",
++ "accent": "#22d3ee",
++ "tagline": "Every rental, one search.",
++ "description": "OpenVibe.Rent will bring rentals into one place: apartments, rooms, commercial space, equipment and services from many listing sites, with alerts, AI summaries that link their sources, and one OpenVibe account for your searches.",
++ "pillars": [
++ {
++ "title": "One search",
++ "text": "Listings from many sites, in one list."
++ },
++ {
++ "title": "Alerts",
++ "text": "Be told when something that fits appears."
++ },
++ {
++ "title": "Clear summaries",
++ "text": "AI summaries that always link the original listing."
++ },
++ {
++ "title": "Your account",
++ "text": "Saved searches and favourites under your OpenVibe account."
++ }
++ ],
++ "keywords": "openvibe rent, rental search, apartments for rent, rooms for rent, commercial rentals, rental alerts",
++ "launch": "verified listing sources and the shared discovery system",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Deals",
++ "url": "https://openvibe.deals",
++ "note": "Deal discovery is the sibling room."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.run.json b/manifests/products/openvibe.run.json
nnew file mode 100644
iindex 00000000..f13f3c6e
---- /dev/null
++++ b/manifests/products/openvibe.run.json
@@@ -0,0 +1,39 @@
++{
++ "domain": "openvibe.run",
++ "tld": "run",
++ "name": "OpenVibe.Run",
++ "icon": "fa-terminal",
++ "accent": "#a78bfa",
++ "tagline": "Run anything, safely.",
++ "description": "OpenVibe.Run will run code, tools and AI agent tasks in sandboxes: quick jobs in WebAssembly, full environments in small virtual machines, with queued jobs, live output, and files through OpenVibe.Download. A free, limited tier for everyone, and the engine the rest of the network uses to get work done.",
++ "pillars": [
++ {
++ "title": "Sandboxed",
++ "text": "Every run is isolated, budgeted and stoppable."
++ },
++ {
++ "title": "Async & streaming",
++ "text": "Queue work, watch its output live, get the result."
++ },
++ {
++ "title": "Agent tasks",
++ "text": "Let AI tools run steps for you, safely."
++ },
++ {
++ "title": "For every site",
++ "text": "The same engine behind tools, pictures, video and chat."
++ }
++ ],
++ "keywords": "openvibe run, code sandbox, run code online, serverless functions, ai agent tasks",
++ "launch": "the sandbox runtime",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Tools",
++ "url": "https://openvibe.tools",
++ "note": "Hundreds of online tools work today."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.services.json b/manifests/products/openvibe.services.json
nnew file mode 100644
iindex 00000000..3e4e61f4
---- /dev/null
++++ b/manifests/products/openvibe.services.json
@@@ -0,0 +1,39 @@
++{
++ "domain": "openvibe.services",
++ "tld": "services",
++ "name": "OpenVibe.Services",
++ "icon": "fa-server",
++ "accent": "#38bdf8",
++ "tagline": "Build with the whole network.",
++ "description": "OpenVibe.Services will be the way into everything programmable on OpenVibe. Start from what you want to build (monitor a page and summarise changes, run an AI agent, process uploads, control a robot, add support to a site) and it assembles the services, keys, permissions and working code for you. Then operate it all in one place: projects, grants, usage, quotas, runs, events, deploys and health, with each service keeping its own data.",
++ "pillars": [
++ {
++ "title": "Discover",
++ "text": "A catalog of services, recipes and templates, by what they do."
++ },
++ {
++ "title": "Build",
++ "text": "Projects, apps, keys, capabilities, playgrounds and starter code."
++ },
++ {
++ "title": "Operate",
++ "text": "Every resource across the network: runs, events, usage, quotas, health."
++ },
++ {
++ "title": "Learn",
++ "text": "Help, Wiki and Codes docs, linked from every page."
++ }
++ ],
++ "keywords": "openvibe services, developer platform, open source backend, api platform, event bus api, ai gateway, website monitoring api, developer console",
++ "launch": "the catalog and console over the services that already run (AI, Events, Media, Search, Sources, Host, Tools)",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Codes",
++ "url": "https://openvibe.codes",
++ "note": "The developer portal and docs work today."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.video.json b/manifests/products/openvibe.video.json
nnew file mode 100644
iindex 00000000..94a164b6
---- /dev/null
++++ b/manifests/products/openvibe.video.json
@@@ -0,0 +1,45 @@
++{
++ "domain": "openvibe.video",
++ "tld": "video",
++ "name": "OpenVibe.Video",
++ "icon": "fa-film",
++ "accent": "#f87171",
++ "tagline": "Your videos, everywhere.",
++ "description": "OpenVibe.Video will be the home of long-form video on the OpenVibe network: watch pages, channels and playlists for uploads and past streams, a creator tool that publishes one upload to many platforms, and AI captions, highlights and editing, delivered by OpenVibe.Media.",
++ "pillars": [
++ {
++ "title": "Watch",
++ "text": "Channels, playlists and search for uploads and past streams."
++ },
++ {
++ "title": "Publish everywhere",
++ "text": "One upload, sent to the platforms you choose, tracked in one place."
++ },
++ {
++ "title": "AI tools",
++ "text": "Captions, translations, highlights and shorts from long videos."
++ },
++ {
++ "title": "From your streams",
++ "text": "Recordings from OpenVibe.Live land here, ready to share."
++ }
++ ],
++ "keywords": "openvibe video, video hosting, youtube alternative, multi-platform upload, ai video tools",
++ "launch": "the video service on OpenVibe.Media",
++ "relationships": {
++ "service": "media-hub",
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Live",
++ "url": "https://openvibe.live",
++ "note": "Streams, recordings and clips live on OpenVibe.Live today."
++ },
++ {
++ "label": "OpenRe.Stream",
++ "url": "https://openre.stream",
++ "note": "Restreaming to many platforms is the sibling product."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.watch.json b/manifests/products/openvibe.watch.json
nnew file mode 100644
iindex 00000000..515ab22f
---- /dev/null
++++ b/manifests/products/openvibe.watch.json
@@@ -0,0 +1,44 @@
++{
++ "domain": "openvibe.watch",
++ "tld": "watch",
++ "name": "OpenVibe.Watch",
++ "icon": "fa-eye",
++ "accent": "#facc15",
++ "tagline": "Watch anything. Hear about what matters.",
++ "description": "OpenVibe.Watch will watch web pages, feeds, APIs, prices and stock, Git releases, OpenVibe activity, videos, livestreams and your own cameras, pull out what changed or what matters, and tell you, your apps or your AI agents. Checks respect site terms and robots rules, heavy work runs on shared workers, and cameras are read by a small agent on your own network so nothing is exposed to the internet.",
++ "pillars": [
++ {
++ "title": "Any source",
++ "text": "Pages, feeds, APIs, repos, videos, streams and cameras."
++ },
++ {
++ "title": "What changed",
++ "text": "Extraction, diffs and conditions, with AI summaries when you want them."
++ },
++ {
++ "title": "Anywhere it matters",
++ "text": "Notifications, events, webhooks and agent triggers."
++ },
++ {
++ "title": "Respectful and safe",
++ "text": "Site terms, robots rules and fair limits on every check."
++ }
++ ],
++ "keywords": "website change monitoring, uptime monitoring api, price drop alerts, restock alerts, release monitoring, youtube video analysis, security camera alerts",
++ "launch": "the watch service on the shared queue and workers",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Sources",
++ "url": "https://github.com/OpenVibers/OpenVibe.Sources",
++ "note": "Permitted-source ingestion that Watch builds on runs today."
++ },
++ {
++ "label": "OpenVibe.Tools",
++ "url": "https://openvibe.tools",
++ "note": "Dozens of tools that run in the browser today."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/openvibe.website.json b/manifests/products/openvibe.website.json
nnew file mode 100644
iindex 00000000..e2dbe793
---- /dev/null
++++ b/manifests/products/openvibe.website.json
@@@ -0,0 +1,44 @@
++{
++ "domain": "openvibe.website",
++ "tld": "website",
++ "name": "OpenVibe.Website",
++ "icon": "fa-globe",
++ "accent": "#22d3ee",
++ "tagline": "Websites that work, not just pages.",
++ "description": "OpenVibe.Website will be an AI-assisted website and small-app builder with a real file tree: generate a site from a description, import one or fork a Git repository, edit HTML, CSS, JavaScript or Markdown by hand or visually, and publish to yourname.openvibe.website or your own domain. Sites can sign people in with OpenVibe, take bookings and forms, show media, run a blog or docs, and add chat, comments, memberships or support, with good SEO built in.",
++ "pillars": [
++ {
++ "title": "Generate or code",
++ "text": "Describe it, import it, fork it, or write it yourself."
++ },
++ {
++ "title": "Real features",
++ "text": "Sign-in, forms, bookings, media, blogs and support from the network."
++ },
++ {
++ "title": "Your files",
++ "text": "Every generated site is readable, editable source."
++ },
++ {
++ "title": "Publish anywhere",
++ "text": "yourname.openvibe.website or your own domain, with rollback."
++ }
++ ],
++ "keywords": "ai website builder, open source website builder, static site builder, portfolio builder, website generator, docs site generator",
++ "launch": "tenant hosting on OpenVibe.Host (built, not launched) and the editor",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Codes",
++ "url": "https://openvibe.codes",
++ "note": "The developer portal, API and SDK docs work today."
++ },
++ {
++ "label": "OpenVibe.Tools",
++ "url": "https://openvibe.tools",
++ "note": "Dozens of tools that run in the browser today."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/realtime.openvibe.network.json b/manifests/products/realtime.openvibe.network.json
nnew file mode 100644
iindex 00000000..675db3c4
---- /dev/null
++++ b/manifests/products/realtime.openvibe.network.json
@@@ -0,0 +1,28 @@
++{
++ "domain": "realtime.openvibe.network",
++ "tld": "realtime",
++ "name": "realtime.openvibe.network",
++ "icon": "fa-satellite-dish",
++ "accent": "#34d399",
++ "tagline": "Closed: realtime delivery is part of OpenVibe.Events.",
++ "legalProfile": "info",
++ "kind": "closed",
++ "description": "OpenVibe.Realtime was planned as a separate service for live updates in the browser. Decision record ADR-005 (accepted 2026-09-22) closed it: browser realtime is part of OpenVibe.Events, as server-sent events with cursor resume at events.openvibe.network/realtime/stream. This address serves only this notice.",
++ "keywords": "openvibe realtime, openvibe events, server-sent events",
++ "relationships": {
++ "noRepo": true,
++ "linksLead": "Realtime delivery for browsers is a part of OpenVibe.Events; the decision and its record are public.",
++ "links": [
++ {
++ "label": "OpenVibe.Events",
++ "url": "https://events.openvibe.network/",
++ "note": "Durable events, subscriptions and signed delivery; browsers subscribe at /realtime/stream."
++ },
++ {
++ "label": "ADR-005: realtime",
++ "url": "https://github.com/OpenVibers/OpenVibe.Contracts/blob/main/docs/adr/ADR-005-realtime.md",
++ "note": "The decision, and when a separate realtime service would be reconsidered."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/products/status.openvibe.network.json b/manifests/products/status.openvibe.network.json
nnew file mode 100644
iindex 00000000..6ba4e00e
---- /dev/null
++++ b/manifests/products/status.openvibe.network.json
@@@ -0,0 +1,34 @@
++{
++ "domain": "status.openvibe.network",
++ "tld": "status",
++ "name": "status.openvibe.network",
++ "icon": "fa-heart-pulse",
++ "accent": "#22c55e",
++ "tagline": "Is OpenVibe up?",
++ "legalProfile": "info",
++ "kind": "status",
++ "description": "OpenVibe.Network checks every OpenVibe service about once a minute (its readiness endpoint and the release it runs) and publishes what it saw at openvibe.network/status. This address points there.",
++ "keywords": "openvibe status, openvibe uptime, openvibe network status",
++ "relationships": {
++ "plannedRepo": "OpenVibe.Network",
++ "linksLead": "Network's own observations, stated as seen: a service that has not been checked recently shows as unknown, never as up.",
++ "links": [
++ {
++ "label": "Status page",
++ "url": "https://openvibe.network/status",
++ "note": "Every service: up, degraded, down, not running or unknown, where it runs and when it was last checked."
++ },
++ {
++ "label": "Status API",
++ "url": "https://openvibe.network/api/v1/status",
++ "note": "The same rows as JSON, for scripts and monitors."
++ },
++ {
++ "label": "Releases",
++ "url": "https://openvibe.network/api/v1/registry/releases",
++ "note": "The release each service runs, and how far behind the shared libraries it is."
++ }
++ ],
++ "statusApi": "https://openvibe.network/api/v1/registry/health"
++ }
++}
ddiff --git a/manifests/products/themes.openvibe.network.json b/manifests/products/themes.openvibe.network.json
nnew file mode 100644
iindex 00000000..a390d056
---- /dev/null
++++ b/manifests/products/themes.openvibe.network.json
@@@ -0,0 +1,40 @@
++{
++ "domain": "themes.openvibe.network",
++ "tld": "themes",
++ "name": "themes.openvibe.network",
++ "icon": "fa-palette",
++ "accent": "#a78bfa",
++ "tagline": "Your look, on every site.",
++ "legalProfile": "info",
++ "description": "themes.openvibe.network will be the theme catalog and API: built-in and community themes, previews, per-site overrides, import/export and the shared design tokens every OpenVibe site renders.",
++ "pillars": [
++ {
++ "title": "Catalog",
++ "text": "Built-in, migrated and community themes with previews."
++ },
++ {
++ "title": "Per-site overrides",
++ "text": "One global choice, tweaked per service if you like."
++ },
++ {
++ "title": "Import / export",
++ "text": "Share a theme as a file or a link."
++ },
++ {
++ "title": "Design tokens",
++ "text": "The versioned tokens behind every OpenVibe page."
++ }
++ ],
++ "keywords": "openvibe themes, openvibe network themes, openvibe platform",
++ "relationships": {
++ "service": "network",
++ "plannedRepo": "OpenVibe.Network",
++ "meanwhile": [
++ {
++ "label": "Pick a theme today",
++ "url": "https://openvibe.network/themes",
++ "note": "Themes already work across the network from openvibe.network."
++ }
++ ]
++ }
++}
ddiff --git a/manifests/services/coupons.json b/manifests/services/coupons.json
iindex 4888c923..78817320 100644
---- a/manifests/services/coupons.json
++++ b/manifests/services/coupons.json
@@@ -25,7 +25,40 @@
"tagline": "Codes that work",
"what": "Coupon codes with merchant matching, restrictions, expiry and real-people validity reports.",
"legalProfile": "info",
-- "position": 17
++ "position": 17,
++ "tld": "coupons",
++ "accent": "#fbbf24",
++ "description": "OpenVibe.Coupons is community-checked coupon discovery: codes matched to stores, validity reported by real people, expirations tracked, and a browser helper on the way.",
++ "pillars": [
++ {
++ "title": "Checked by people",
++ "text": "Codes that worked, codes that didn't, reported honestly."
++ },
++ {
++ "title": "Store matching",
++ "text": "The right codes for the store you are on."
++ },
++ {
++ "title": "Expiry tracking",
++ "text": "Dead codes go away."
++ },
++ {
++ "title": "Browser helper",
++ "text": "Apply codes where you shop — coming with the site."
++ }
++ ],
++ "keywords": "openvibe coupons, coupon codes, community coupons",
++ "launch": "a person to verify a coupons source's terms and switch it on",
++ "relationships": {
++ "plannedRepo": "OpenVibe.Coupons",
++ "meanwhile": [
++ {
++ "label": "Deals",
++ "url": "https://openvibe.deals",
++ "note": "Deal discovery is the sibling of this room."
++ }
++ ]
++ }
},
"capabilities": [
"coupons.coupon.submit",
ddiff --git a/manifests/services/deals.json b/manifests/services/deals.json
iindex 0e75b311..48d90e9d 100644
---- a/manifests/services/deals.json
++++ b/manifests/services/deals.json
@@@ -25,7 +25,40 @@
"tagline": "Deals worth sharing",
"what": "Deals submitted and voted on by the community, with source, price and freshness always shown.",
"legalProfile": "info",
-- "position": 16
++ "position": 16,
++ "tld": "deals",
++ "accent": "#f97316",
++ "description": "OpenVibe.Deals is community deal discovery for the OpenVibe network: deals people vote on, hotness that reflects real interest, keyword alerts, price comparisons and honest discussion.",
++ "pillars": [
++ {
++ "title": "Voted, not paid for",
++ "text": "Deals rise because people like them."
++ },
++ {
++ "title": "Alerts",
++ "text": "Tell it what you want; it tells you when it is cheap."
++ },
++ {
++ "title": "Compare",
++ "text": "Summaries and comparisons, with sources."
++ },
++ {
++ "title": "Discuss",
++ "text": "Threads through OpenVibe.Community."
++ }
++ ],
++ "keywords": "openvibe deals, community deals, deal alerts",
++ "launch": "a person to verify a deals source's terms and switch it on",
++ "relationships": {
++ "plannedRepo": "OpenVibe.Deals",
++ "meanwhile": [
++ {
++ "label": "Coupons",
++ "url": "https://openvibe.coupons",
++ "note": "The coupon room is the sibling of this one."
++ }
++ ]
++ }
},
"capabilities": [
"deals.offer.submit",
ddiff --git a/manifests/services/host.json b/manifests/services/host.json
iindex 37145246..7e29ee2f 100644
---- a/manifests/services/host.json
++++ b/manifests/services/host.json
@@@ -25,7 +25,45 @@
"tagline": "Deploy and host",
"what": "The network's deployment plane first, then simple hosting for community sites, bots and mods.",
"legalProfile": "hosting",
-- "position": 15
++ "position": 15,
++ "tld": "host",
++ "accent": "#94a3b8",
++ "description": "OpenVibe.Host is the network's deployment tooling and, next, static site hosting for OpenVibe projects. The tooling (ovhost) already runs on the network's host: deploys with automatic rollback, backups and restore drills for the network's own services. The hosting part will let a project upload a folder of HTML, CSS, JavaScript and images, serve it at <site>.openvibe.host and roll back to any earlier deploy in one step. Static files only: nothing uploaded is run as code.",
++ "pillars": [
++ {
++ "title": "Static sites",
++ "text": "Upload a folder or a .tar.gz of HTML, CSS, JavaScript, images and fonts; it is served at <site>.openvibe.host over HTTPS."
++ },
++ {
++ "title": "Every deploy kept",
++ "text": "Deploys are immutable and content-addressed: switch to, or roll back to, any earlier one in one step."
++ },
++ {
++ "title": "Your own domain",
++ "text": "Custom domains you prove with a DNS TXT record; their certificates are issued by an operator for now."
++ },
++ {
++ "title": "Static only",
++ "text": "No build step and no server-side code: nothing you upload is run. Running bots, mods or apps is not offered; that needs isolation the network has not built yet."
++ }
++ ],
++ "keywords": "openvibe host, static site hosting, deployment, openvibe projects",
++ "launch": "the owner's go on its launch checklist",
++ "relationships": {
++ "plannedRepo": "OpenVibe.Host",
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Host on GitHub",
++ "url": "https://github.com/OpenVibers/OpenVibe.Host",
++ "note": "The code, the launch checklist and the threat review."
++ },
++ {
++ "label": "Build on OpenVibe",
++ "url": "https://openvibe.codes",
++ "note": "Projects, apps and credentials on OpenVibe.Codes, the developer portal."
++ }
++ ]
++ }
},
"capabilities": [
"host.site.manage",
ddiff --git a/manifests/services/news.json b/manifests/services/news.json
iindex b09d561f..758b0945 100644
---- a/manifests/services/news.json
++++ b/manifests/services/news.json
@@@ -25,7 +25,40 @@
"tagline": "Sourced, not spun",
"what": "Source-backed stories: clustered coverage, cited summaries, multiple perspectives and timelines.",
"legalProfile": "info",
-- "position": 10
++ "position": 10,
++ "tld": "news",
++ "accent": "#fb923c",
++ "description": "OpenVibe.News aggregates the news with transparency: sourced stories, summaries that show their sources, multiple angles side by side, topic pages and framing you control — not an algorithm you can't see.",
++ "pillars": [
++ {
++ "title": "Sources first",
++ "text": "Every summary links to what it was built from."
++ },
++ {
++ "title": "Many angles",
++ "text": "Read the same story from several perspectives, side by side."
++ },
++ {
++ "title": "Your framing",
++ "text": "You choose the lens; nothing is chosen for you silently."
++ },
++ {
++ "title": "Topics",
++ "text": "Follow topics across sources, with your OpenVibe account."
++ }
++ ],
++ "keywords": "openvibe news, transparent news, news aggregation, multiple perspectives",
++ "launch": "a person to verify a news source's terms and an editor to publish the first reviewed story",
++ "relationships": {
++ "plannedRepo": "OpenVibe.News",
++ "meanwhile": [
++ {
++ "label": "Network updates",
++ "url": "https://openvibe.live/updates",
++ "note": "What is happening across OpenVibe."
++ }
++ ]
++ }
},
"capabilities": [
"news.story.create",
ddiff --git a/manifests/services/openre.json b/manifests/services/openre.json
iindex 362a929b..ca43d34f 100644
---- a/manifests/services/openre.json
++++ b/manifests/services/openre.json
@@@ -26,7 +26,85 @@
"tagline": "Restream anywhere",
"what": "Ingest once and send the stream to every platform at once; keys, destinations and output health in one place.",
"legalProfile": "streaming",
-- "position": 18
++ "position": 18,
++ "tld": "stream",
++ "accent": "#ef4444",
++ "description": "OpenRe.Stream is OpenVibe's open streaming engine, split out of OpenVibe.Live. Go live from a web browser with no OBS, no downloads and no follower minimum, or send RTMP or WHIP from any encoder, and restream to Twitch, YouTube, Kick and any RTMP, RTMPS or SRT destination. Browser broadcasts start on OpenVibe.Live today, and move to OpenRe.Stream ingest channel by channel.",
++ "pillars": [
++ {
++ "title": "Browser go-live",
++ "text": "Stream from a browser over WebRTC and WHIP, with nothing to install and no follower minimum."
++ },
++ {
++ "title": "Ingest",
++ "text": "RTMP ingest with keys and lifecycle in one place, plus WHIP/WebRTC; SRT ingest to follow."
++ },
++ {
++ "title": "Restream",
++ "text": "Twitch, YouTube, Kick and any RTMP, RTMPS or SRT destination."
++ },
++ {
++ "title": "Open API and SDK",
++ "text": "Streams, sessions and destinations through the OpenVibe API and SDK; run it yourself from GitHub."
++ }
++ ],
++ "faq": [
++ {
++ "question": "Can I stream without OBS?",
++ "answer": "Yes. Go live from your browser on OpenVibe.Live: press Go Live, allow your camera and microphone, and you are live. There is nothing to install."
++ },
++ {
++ "question": "Do I need followers or subscribers to go live?",
++ "answer": "No. Every OpenVibe account can go live from the first minute, with no follower, subscriber or past-stream requirement."
++ },
++ {
++ "question": "Can I stream from a Chromebook or a phone?",
++ "answer": "Yes. The broadcaster runs in Chrome, Edge, Firefox and Safari, on ChromeOS, Android and iOS as well as Windows, macOS and Linux."
++ },
++ {
++ "question": "Can I restream to Twitch, YouTube and Kick at the same time?",
++ "answer": "Yes. One broadcast can go to Twitch, YouTube, Kick and any RTMP, RTMPS or SRT server at once."
++ },
++ {
++ "question": "Can I use OBS if I want to?",
++ "answer": "Yes. OBS and any RTMP encoder work, and so do WHIP encoders (OBS 30 and later, ffmpeg, GStreamer)."
++ }
++ ],
++ "keywords": "go live from browser, stream without obs, browser streaming, no follower minimum, stream from chromebook, webrtc streaming, whip, rtmp ingest, srt, restream to twitch youtube kick, open source streaming",
++ "highlight": {
++ "title": "Go live from your browser — no OBS needed",
++ "text": "Press Go Live, allow your camera and microphone, and you're live, with your own channel and chat. There's nothing to install and no follower or subscriber minimum. It works on a Chromebook, a laptop or a phone.",
++ "points": [
++ "No OBS, plug-ins or capture card: WebRTC in the browser, with under a second of delay",
++ "No follower, subscriber or past-stream requirement",
++ "Share your screen, a window or a tab, with your camera over it",
++ "When you want scenes, use OBS or any RTMP or WHIP encoder"
++ ],
++ "cta": {
++ "label": "Go live now on OpenVibe.Live",
++ "url": "https://openvibe.live/broadcast"
++ },
++ "more": {
++ "label": "How browser streaming works",
++ "url": "https://openvibe.live/docs/go-live-in-your-browser"
++ }
++ },
++ "launch": "the stream slots to move from OpenVibe.Live's ingest to OpenRe.Stream, one at a time; no stream uses it yet",
++ "relationships": {
++ "plannedRepo": "OpenRe.Stream",
++ "meanwhile": [
++ {
++ "label": "Go live on OpenVibe.Live",
++ "url": "https://openvibe.live/broadcast",
++ "note": "Ingest and restreaming run inside Live today."
++ },
++ {
++ "label": "Restreaming docs",
++ "url": "https://openvibe.live/docs/restreaming",
++ "note": "How the restream paths work."
++ }
++ ]
++ }
},
"capabilities": [
"openre.stream.read",
ddiff --git a/manifests/services/reviews.json b/manifests/services/reviews.json
iindex 1572d92e..1b5823c6 100644
---- a/manifests/services/reviews.json
++++ b/manifests/services/reviews.json
@@@ -25,7 +25,40 @@
"tagline": "Reviews with receipts",
"what": "Review signals gathered across sources with provenance, pros and cons, and community discussion. No invented ratings.",
"legalProfile": "ugc",
-- "position": 11
++ "position": 11,
++ "tld": "reviews",
++ "accent": "#facc15",
++ "description": "OpenVibe.Reviews gathers what people say about products, services, employers, hosts and communities across many sources — with provenance, without fake ratings — and lets the community discuss it.",
++ "pillars": [
++ {
++ "title": "Entity pages",
++ "text": "One page per product, service or place, built from many sources."
++ },
++ {
++ "title": "Provenance",
++ "text": "Every quote links back to where it was said."
++ },
++ {
++ "title": "No fake ratings",
++ "text": "Summaries, not invented stars."
++ },
++ {
++ "title": "Discussion",
++ "text": "Community threads through OpenVibe.Community."
++ }
++ ],
++ "keywords": "openvibe reviews, review aggregation, honest reviews",
++ "launch": "a person to verify a review source's terms and an editor to publish the first reviewed summary",
++ "relationships": {
++ "plannedRepo": "OpenVibe.Reviews",
++ "meanwhile": [
++ {
++ "label": "Talk about it",
++ "url": "https://openvibe.community",
++ "note": "Community threads are the first stop."
++ }
++ ]
++ }
},
"capabilities": [
"reviews.entity.resolve",
ddiff --git a/manifests/services/space.json b/manifests/services/space.json
iindex 53871b9b..f88786f8 100644
---- a/manifests/services/space.json
++++ b/manifests/services/space.json
@@@ -22,7 +22,40 @@
"tagline": "Forums, boards and threads",
"what": "OpenVibe's forums: public and members-only spaces, threads, posts, votes, categories and the Discord relay, moved off Community (plan T10, D1/D2).",
"legalProfile": "ugc",
-- "position": 19
++ "position": 19,
++ "tld": "space",
++ "accent": "#818cf8",
++ "description": "OpenVibe.Space will be the forums of the OpenVibe network: create a forum for any topic, with the depth of classic boards like XenForo and phpBB, the feel of a modern community, and AI help for topics, research and moderation. The forums on OpenVibe.Community move here when it launches.",
++ "pillars": [
++ {
++ "title": "Any forum",
++ "text": "Anyone can start one, for any topic."
++ },
++ {
++ "title": "Classic depth",
++ "text": "Sub-forums, threads, ranks, moderation tools."
++ },
++ {
++ "title": "AI assist",
++ "text": "Topic ideas, research and summaries, always labelled."
++ },
++ {
++ "title": "One account",
++ "text": "Your OpenVibe identity, everywhere on the network."
++ }
++ ],
++ "keywords": "openvibe space, forums, create a forum, community boards, xenforo alternative, phpbb alternative",
++ "launch": "the forum engine moving over from OpenVibe.Community",
++ "relationships": {
++ "noRepo": true,
++ "meanwhile": [
++ {
++ "label": "OpenVibe.Community",
++ "url": "https://openvibe.community",
++ "note": "Forums, pastes and people live there today."
++ }
++ ]
++ }
},
"capabilities": [
"space.forum.read",
ddiff --git a/manifests/services/tips.json b/manifests/services/tips.json
iindex a4a52f73..321581c2 100644
---- a/manifests/services/tips.json
++++ b/manifests/services/tips.json
@@@ -25,7 +25,40 @@
"tagline": "Support creators",
"what": "Tips, goals, paid messages and alerts for creators, settled by the network's billing ledger.",
"legalProfile": "streaming",
-- "position": 12
++ "position": 12,
++ "tld": "tips",
++ "accent": "#4ade80",
++ "description": "OpenVibe.Tips is the tipping and alerts layer of the OpenVibe network: tips, donation goals, superchats, paid text-to-speech and media requests, with alert overlays for creators.",
++ "pillars": [
++ {
++ "title": "Tips & goals",
++ "text": "Tip a creator, fund a goal, see it land on stream."
++ },
++ {
++ "title": "Superchat & TTS",
++ "text": "Paid messages read out on stream, through OpenVibe.Chat."
++ },
++ {
++ "title": "Alert overlays",
++ "text": "Overlays for OBS and the browser, themed with the network."
++ },
++ {
++ "title": "One ledger",
++ "text": "Every transaction on the network's own ledger, visible to you."
++ }
++ ],
++ "keywords": "openvibe tips, tip streamers, donation alerts, superchat",
++ "launch": "the payments cutover to OpenVibe.Billing and a security review; tips keep working on OpenVibe.Live meanwhile",
++ "relationships": {
++ "plannedRepo": "OpenVibe.Tips",
++ "meanwhile": [
++ {
++ "label": "Tip with Vibes on OpenVibe.Live",
++ "url": "https://openvibe.live",
++ "note": "Tips already work on Live today."
++ }
++ ]
++ }
},
"capabilities": [
"tips.profile.get",
ddiff --git a/manifests/services/trade.json b/manifests/services/trade.json
iindex 278eaabb..f3990e76 100644
---- a/manifests/services/trade.json
++++ b/manifests/services/trade.json
@@@ -25,7 +25,40 @@
"tagline": "Watchlists and alerts",
"what": "Informational watchlists, sourced market context and alerts. No custody, no order execution.",
"legalProfile": "ugc",
-- "position": 14
++ "position": 14,
++ "tld": "trade",
++ "accent": "#60a5fa",
++ "description": "OpenVibe.Trade is the market intelligence room of the OpenVibe network: watchlists, alerts, summaries and community discussion for markets, collectibles and deals — information and tools, never advice.",
++ "pillars": [
++ {
++ "title": "Watchlists & alerts",
++ "text": "Track what you care about, get told when it moves."
++ },
++ {
++ "title": "Summaries",
++ "text": "What happened and why, with sources."
++ },
++ {
++ "title": "Community",
++ "text": "Discuss with people, not bots."
++ },
++ {
++ "title": "Not advice",
++ "text": "Tools and information only — always labelled as such."
++ }
++ ],
++ "keywords": "openvibe trade, watchlists, market alerts, community trading tools",
++ "launch": "a verified data source and editors adding the first instruments",
++ "relationships": {
++ "plannedRepo": "OpenVibe.Trade",
++ "meanwhile": [
++ {
++ "label": "Deals",
++ "url": "https://openvibe.deals",
++ "note": "Deal discovery is the sibling room."
++ }
++ ]
++ }
},
"capabilities": [
"trade.instrument.resolve",
ddiff --git a/manifests/services/vip.json b/manifests/services/vip.json
iindex e93c01e2..3b45a67e 100644
---- a/manifests/services/vip.json
++++ b/manifests/services/vip.json
@@@ -25,7 +25,40 @@
"tagline": "Memberships and perks",
"what": "Creator and network memberships: plans, perks and benefits recognised on every OpenVibe site.",
"legalProfile": "account",
-- "position": 13
++ "position": 13,
++ "tld": "vip",
++ "accent": "#e879f9",
++ "description": "OpenVibe.VIP is the membership layer of the OpenVibe network: creator memberships, channel subscriptions, VIP tiers and perks that unlock across Live, Community, Blog and more.",
++ "pillars": [
++ {
++ "title": "Creator memberships",
++ "text": "Subscribe to the people you watch; perks follow you everywhere."
++ },
++ {
++ "title": "Network VIP",
++ "text": "One membership that supports the whole network."
++ },
++ {
++ "title": "Perks",
++ "text": "Badges, emotes, early access, gated posts — set by creators."
++ },
++ {
++ "title": "Transparent",
++ "text": "What it costs and where it goes, in the open."
++ }
++ ],
++ "keywords": "openvibe vip, creator memberships, subscriptions",
++ "launch": "the payments cutover to OpenVibe.Billing; checkout stays closed until then",
++ "relationships": {
++ "plannedRepo": "OpenVibe.VIP",
++ "meanwhile": [
++ {
++ "label": "Subscriptions on OpenVibe.Live",
++ "url": "https://openvibe.live",
++ "note": "Channel subscriptions exist on Live today."
++ }
++ ]
++ }
},
"capabilities": [
"vip.plan.create",
ddiff --git a/test/fixtures/sites-domains.json b/test/fixtures/sites-domains.json
nnew file mode 100644
iindex 00000000..aeae5f9d
---- /dev/null
++++ b/test/fixtures/sites-domains.json
@@@ -0,0 +1,39 @@
++{
++ "_about": "The 33 domains of OpenVibe.Sites sites.json (origin/main c2fda6c, 2026-09-29), snapshotted for the catalog home test (plan T11 lane D). Each has exactly one home: a service manifest site with a tld, or manifests/products/<domain>.json.",
++ "source": "OpenVibers/OpenVibe.Sites@c2fda6c:sites.json",
++ "domains": [
++ "openvibe.news",
++ "openvibe.reviews",
++ "openvibe.tips",
++ "openvibe.vip",
++ "openvibe.trade",
++ "openvibe.host",
++ "openvibe.deals",
++ "openvibe.coupons",
++ "openre.stream",
++ "auth.openvibe.network",
++ "api.openvibe.network",
++ "admin.openvibe.network",
++ "themes.openvibe.network",
++ "realtime.openvibe.network",
++ "ai.openvibe.network",
++ "ai.openvibe.services",
++ "status.openvibe.network",
++ "openvibe.pics",
++ "openvibe.download",
++ "openvibe.space",
++ "openvibe.food",
++ "openvibe.quest",
++ "openvibe.rent",
++ "openvibe.homes",
++ "openvibe.services",
++ "openvibe.run",
++ "openvibe.video",
++ "openvibe.website",
++ "openvibe.help",
++ "openvibe.bot",
++ "openvibe.watch",
++ "openvibe.actor",
++ "openvibe.events"
++ ]
++}
ddiff --git a/test/product-catalog.test.js b/test/product-catalog.test.js
nnew file mode 100644
iindex 00000000..27eb96e5
---- /dev/null
++++ b/test/product-catalog.test.js
@@@ -0,0 +1,43 @@
++'use strict';
++// The product catalog (plan T11 lane D): every domain the OpenVibe.Sites catalog held has exactly one
++// home in Contracts, a service manifest's site carrying a tld or manifests/products/<domain>.json,
++// and every home validates.
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const contracts = require('..');
++const { services, products } = contracts;
++
++const ROOT = path.join(__dirname, '..');
++let checks = 0;
++const ok = (cond, msg) => { assert.ok(cond, msg); checks++; };
++
++const DIR = path.join(ROOT, 'manifests/products');
++for (const f of fs.readdirSync(DIR).filter(f => f.endsWith('.json'))) {
++ const p = JSON.parse(fs.readFileSync(path.join(DIR, f), 'utf8'));
++ const r = contracts.validate('registry.product@1', p);
++ ok(r.valid, `product ${f}: ${JSON.stringify(r.errors)}`);
++ ok(f === `${p.domain}.json`, `${f} is named after its domain`);
++}
++for (const m of services.manifests) {
++ ok(contracts.validate('registry.service-manifest@1', m).valid, `service manifest ${m.id} validates`);
++ if (m.site && m.site.tld) ok((m.domains || []).includes(products.siteDomain(m)), `${m.id}: its site domain ${products.siteDomain(m)} is one of its domains`);
++}
++
++const homes = new Map();
++for (const row of products.catalog()) homes.set(row.domain, [...(homes.get(row.domain) || []), `${row.home}:${row.id}`]);
++for (const [domain, list] of homes) ok(list.length === 1, `${domain} has one home, not ${list.join(', ')}`);
++
++const snapshot = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures/sites-domains.json'), 'utf8')).domains;
++ok(snapshot.length === 33 && new Set(snapshot).size === 33, 'the Sites snapshot holds 33 distinct domains');
++for (const d of snapshot) ok((homes.get(d) || []).length === 1, `${d} (OpenVibe.Sites) has exactly one Contracts home: ${(homes.get(d) || ['none']).join(', ')}`);
++
++for (const p of products.manifests) {
++ const rel = p.relationships || {};
++ const serving = services.manifests.filter(m => (m.domains || []).includes(p.domain)).map(m => m.id);
++ if (rel.service) ok(!!services.get(rel.service), `${p.domain}: relationships.service ${rel.service} is a service manifest`);
++ if (serving.length) ok(serving.length === 1 && rel.service === serving[0], `${p.domain} is served by ${serving.join(', ')}, and relationships.service says so`);
++ ok(!(rel.noRepo && rel.plannedRepo), `${p.domain}: a repository is either planned or absent`);
++ if (p.kind) ok((rel.links || []).length > 0, `${p.domain} is ${p.kind}: its links say where to go`);
++}
++console.log(`product catalog: ${checks} checks passed (${homes.size} catalog domains)`);
ddiff --git a/test/run.js b/test/run.js
iindex 1fd8f3f6..87df3086 100644
---- a/test/run.js
++++ b/test/run.js
@@@ -859,6 +859,8 @@ sub(path.join(__dirname, 'openapi.test.js'));
sub(path.join(__dirname, 'loyalty-policy.test.js'));
// Presence is ephemeral, in Chat's delivery plane; no Events topic (ADR-005 amendment 1).
sub(path.join(__dirname, 'presence-policy.test.js'));
++// Every OpenVibe.Sites catalog domain has one home in Contracts (plan T11 lane D).
++sub(path.join(__dirname, 'product-catalog.test.js'));
// ── Generated output and compatibility gate ──────────────────────────────
sub(path.join(__dirname, 'platform-fabric.test.js'));