openvibers/openvibe.contracts Commits
v0.42.0: service manifests carry internalOrigin (the loopback address the registry polls), exposure (where the service can be reached today: state, publicSite, note, library package/repo) and site (name, icon, tagline, what, legalProfile, position, host), with the values Network hard-coded; ready for network, media and games; tests: every manifest has an exposure, running services unique loopback ports, libraries name package and repo, unique site positions (WS-C task 1)
Changes
@@@ -4,6 +4,17 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) thfrom `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,the notes were in the tag and commit messages (`git tag -n1`).++## 0.42.0 — 2026-09-25++++The service manifests carry what Network's registry and site list used to hard-code (roadmap WS-C task 1, "manifest-derived registry"):++++- **`internalOrigin`**: the loopback address a running service answers on, which the registry polls. An `OV_<ID>_INTERNAL_URL` loopback override can still replace it.++- **`exposure`**: where the service can be reached today. `state` is live, internal, library, repository, placeholder or retired; `publicSite` says what the public domain answers (the service, a placeholder, or nothing); there is an optional `note`; libraries also give `package` and `repo`. This is separate from `status` (maturity).++- **`site`**: how a site is presented, with `name`, `icon`, `tagline`, `what`, `legalProfile` (the legal wording of /terms, /privacy, /dmca), `position`, and `host` for a site without a public origin. A service without `site` is not a site.++- **`ready`** is added to the network, media and games manifests (`/api/ready`).++++The values are exactly what Network hard-coded. A service going public is now one manifest edit here. The test requires every first-party manifest to have an exposure, every running service to have a unique loopback port, libraries to name their package and repository, and site positions to be unique. The additions are optional in the schema, so this release is additive.++## 0.41.1 — 2026-09-25Plain-language descriptions for `ai.preferences`, `chat.preferences`, `games.progress.summary` and `live.profile`. my.openvibe.network now shows these descriptions to people on its AI & Data tab. No schema change.@@@ -4,23 +4,194 @@"title": "ServiceManifest","description": "What a service is, where it lives and what it offers. The ecosystem registry is built from these, not from hard-coded route maps.","type": "object",-- "required": ["id", "version", "status", "repository", "domains", "capabilities", "eventsProduced", "eventsConsumed", "namespacesOwned"],++ "required": [++ "id",++ "version",++ "status",++ "repository",++ "domains",++ "capabilities",++ "eventsProduced",++ "eventsConsumed",++ "namespacesOwned"++ ],"additionalProperties": false,"properties": {-- "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$" },-- "name": { "type": "string" },-- "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?$" },-- "status": { "enum": ["placeholder", "alpha", "beta", "stable", "degraded", "retired"] },-- "repository": { "type": "string", "pattern": "^OpenVibers/[A-Za-z0-9._-]+$" },-- "domains": { "type": "array", "items": { "type": "string" } },-- "publicOrigin": { "type": "string", "format": "uri" },-- "health": { "type": "string", "pattern": "^/" },-- "ready": { "type": "string", "pattern": "^/" },-- "capabilities": { "type": "array", "items": { "type": "string" } },-- "eventsProduced": { "type": "array", "items": { "type": "string" } },-- "eventsConsumed": { "type": "array", "items": { "type": "string" } },-- "namespacesOwned": { "type": "array", "items": { "type": "string" } },-- "contractRanges": { "type": "object", "additionalProperties": { "type": "string" } },-- "notes": { "type": "string" }++ "id": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,39}$"++ },++ "name": {++ "type": "string"++ },++ "version": {++ "type": "string",++ "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?$"++ },++ "status": {++ "enum": [++ "placeholder",++ "alpha",++ "beta",++ "stable",++ "degraded",++ "retired"++ ]++ },++ "repository": {++ "type": "string",++ "pattern": "^OpenVibers/[A-Za-z0-9._-]+$"++ },++ "domains": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "publicOrigin": {++ "type": "string",++ "format": "uri"++ },++ "health": {++ "type": "string",++ "pattern": "^/"++ },++ "ready": {++ "type": "string",++ "pattern": "^/"++ },++ "internalOrigin": {++ "type": "string",++ "pattern": "^http://(127\\.0\\.0\\.1|localhost|\\[::1\\]):[0-9]{2,5}$",++ "description": "Where the service answers on its host (loopback only): the address the registry polls health and readiness from. A host inventory or an OV_<ID>_INTERNAL_URL loopback override may replace it; a service with nothing to run has none."++ },++ "exposure": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "state"++ ],++ "description": "Where the service can actually be reached today, which is separate from its maturity (status): live (its public domain serves it), internal (loopback only; the domain, if any, still serves a placeholder), library (a released package), repository (code with CI, nothing released or run), placeholder (charter only) or retired. When a service goes public, this changes in the same release that points its domain at it.",++ "properties": {++ "state": {++ "enum": [++ "live",++ "internal",++ "library",++ "repository",++ "placeholder",++ "retired"++ ]++ },++ "publicSite": {++ "enum": [++ "service",++ "placeholder",++ null++ ],++ "description": "What the public domain answers today: the service itself, a placeholder page, or nothing (no public domain)."++ },++ "note": {++ "type": "string",++ "maxLength": 300++ },++ "package": {++ "type": "string",++ "pattern": "^[a-z0-9@/._-]+$",++ "description": "For a library: the npm package name."++ },++ "repo": {++ "type": "string",++ "pattern": "^[A-Za-z0-9._-]+$",++ "description": "For a library: the OpenVibers repository its release tags live in."++ }++ }++ },++ "site": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "name",++ "icon",++ "tagline",++ "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.",++ "properties": {++ "name": {++ "type": "string",++ "maxLength": 40,++ "description": "Short name after 'OpenVibe.' (or a full name containing a dot)."++ },++ "icon": {++ "type": "string",++ "pattern": "^[a-z0-9-]+$"++ },++ "tagline": {++ "type": "string",++ "maxLength": 60++ },++ "what": {++ "type": "string",++ "maxLength": 300++ },++ "legalProfile": {++ "enum": [++ "streaming",++ "tools",++ "ugc",++ "games",++ "hosting",++ "account",++ "info"++ ],++ "description": "Which legal wording its /terms, /privacy and /dmca use (openvibe-shared/legal)."++ },++ "position": {++ "type": "integer",++ "minimum": 0,++ "description": "Order in site lists."++ },++ "host": {++ "type": "string",++ "pattern": "^[a-z0-9.-]+$",++ "description": "Only while the manifest has no publicOrigin."++ }++ }++ },++ "capabilities": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "eventsProduced": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "eventsConsumed": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "namespacesOwned": {++ "type": "array",++ "items": {++ "type": "string"++ }++ },++ "contractRanges": {++ "type": "object",++ "additionalProperties": {++ "type": "string"++ }++ },++ "notes": {++ "type": "string"++ }}}@@@ -453,6 +453,106 @@"type": "string","pattern": "^/"},++ "internalOrigin": {++ "type": "string",++ "pattern": "^http://(127\\.0\\.0\\.1|localhost|\\[::1\\]):[0-9]{2,5}$",++ "description": "Where the service answers on its host (loopback only): the address the registry polls health and readiness from. A host inventory or an OV_<ID>_INTERNAL_URL loopback override may replace it; a service with nothing to run has none."++ },++ "exposure": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "state"++ ],++ "description": "Where the service can actually be reached today, which is separate from its maturity (status): live (its public domain serves it), internal (loopback only; the domain, if any, still serves a placeholder), library (a released package), repository (code with CI, nothing released or run), placeholder (charter only) or retired. When a service goes public, this changes in the same release that points its domain at it.",++ "properties": {++ "state": {++ "enum": [++ "live",++ "internal",++ "library",++ "repository",++ "placeholder",++ "retired"++ ]++ },++ "publicSite": {++ "enum": [++ "service",++ "placeholder",++ null++ ],++ "description": "What the public domain answers today: the service itself, a placeholder page, or nothing (no public domain)."++ },++ "note": {++ "type": "string",++ "maxLength": 300++ },++ "package": {++ "type": "string",++ "pattern": "^[a-z0-9@/._-]+$",++ "description": "For a library: the npm package name."++ },++ "repo": {++ "type": "string",++ "pattern": "^[A-Za-z0-9._-]+$",++ "description": "For a library: the OpenVibers repository its release tags live in."++ }++ }++ },++ "site": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "name",++ "icon",++ "tagline",++ "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.",++ "properties": {++ "name": {++ "type": "string",++ "maxLength": 40,++ "description": "Short name after 'OpenVibe.' (or a full name containing a dot)."++ },++ "icon": {++ "type": "string",++ "pattern": "^[a-z0-9-]+$"++ },++ "tagline": {++ "type": "string",++ "maxLength": 60++ },++ "what": {++ "type": "string",++ "maxLength": 300++ },++ "legalProfile": {++ "enum": [++ "streaming",++ "tools",++ "ugc",++ "games",++ "hosting",++ "account",++ "info"++ ],++ "description": "Which legal wording its /terms, /privacy and /dmca use (openvibe-shared/legal)."++ },++ "position": {++ "type": "integer",++ "minimum": 0,++ "description": "Order in site lists."++ },++ "host": {++ "type": "string",++ "pattern": "^[a-z0-9.-]+$",++ "description": "Only while the manifest has no publicOrigin."++ }++ }++ },"capabilities": {"type": "array","items": {@@@ -143,6 +143,53 @@ export interface ServiceManifest {publicOrigin?: string;health?: string;ready?: string;++ /**++ * Where the service answers on its host (loopback only): the address the registry polls health and readiness from. A host inventory or an OV_<ID>_INTERNAL_URL loopback override may replace it; a service with nothing to run has none.++ */++ internalOrigin?: string;++ /**++ * Where the service can actually be reached today, which is separate from its maturity (status): live (its public domain serves it), internal (loopback only; the domain, if any, still serves a placeholder), library (a released package), repository (code with CI, nothing released or run), placeholder (charter only) or retired. When a service goes public, this changes in the same release that points its domain at it.++ */++ exposure?: {++ state: "live" | "internal" | "library" | "repository" | "placeholder" | "retired";++ /**++ * What the public domain answers today: the service itself, a placeholder page, or nothing (no public domain).++ */++ publicSite?: "service" | "placeholder" | null;++ note?: string;++ /**++ * For a library: the npm package name.++ */++ package?: string;++ /**++ * For a library: the OpenVibers repository its release tags live in.++ */++ 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.++ */++ site?: {++ /**++ * Short name after 'OpenVibe.' (or a full name containing a dot).++ */++ name: string;++ icon: string;++ tagline: string;++ what: string;++ /**++ * Which legal wording its /terms, /privacy and /dmca use (openvibe-shared/legal).++ */++ legalProfile: "streaming" | "tools" | "ugc" | "games" | "hosting" | "account" | "info";++ /**++ * Order in site lists.++ */++ position?: number;++ /**++ * Only while the manifest has no publicOrigin.++ */++ host?: string;++ };capabilities: string[];eventsProduced: string[];eventsConsumed: string[];@@@ -13,6 +13,12 @@"publicOrigin": "https://ai.openvibe.network","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4700",++ "exposure": {++ "state": "internal",++ "publicSite": null,++ "note": "called by other services at 127.0.0.1:4700; ai.openvibe.network serves the OpenVibe.Sites placeholder page"++ },"capabilities": ["ai.run.create","ai.run.read",@@@ -27,6 +33,8 @@"ai.run.cached"],"eventsConsumed": [],-- "namespacesOwned": ["ai.usage_summary"],++ "namespacesOwned": [++ "ai.usage_summary"++ ],"notes": "Providers, models, routing profiles, versioned prompt templates and workflows, runs, request logs, citations, scoped cache, quotas and audit. Workflow output is a draft/evidence package attributed to workflow + model + run. ai.run.queued|succeeded|failed|cached have payload contracts (v0.30.0, status planned): AI publishes them once its Events client is wired in; it publishes none today. ai.run.create takes ai.run-request@1 and returns ai.run@1."}@@@ -13,6 +13,12 @@"publicOrigin": "https://billing.openvibe.network","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4600",++ "exposure": {++ "state": "live",++ "publicSite": "service",++ "note": "the public domain is the staff console; accounts pay through each product"++ },"capabilities": ["billing.intent.create","billing.transfer.create",@@@ -13,6 +13,19 @@"publicOrigin": "https://openvibe.blog","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4810",++ "exposure": {++ "state": "live",++ "publicSite": "service"++ },++ "site": {++ "name": "Blog",++ "icon": "blog",++ "tagline": "Long-form, yours to keep",++ "what": "The official OpenVibe blog and a blog for every member: drafts, scheduling, media, comments and feeds.",++ "legalProfile": "info",++ "position": 8++ },"capabilities": ["blog.blog.create","blog.blog.configure",@@@ -7,7 +7,9 @@"name": "OpenVibe.Chat","status": "alpha","repository": "OpenVibers/OpenVibe.Chat",-- "domains": ["openvibe.chat"],++ "domains": [++ "openvibe.chat"++ ],"publicOrigin": "https://openvibe.chat","capabilities": ["chat.live_bridge.write",@@@ -23,8 +25,27 @@"eventsConsumed": ["network.user.token_valid_after"],-- "namespacesOwned": ["chat.preferences", "chat.tts_defaults", "chat.dm_settings", "chat.presence_prefs"],++ "namespacesOwned": [++ "chat.preferences",++ "chat.tts_defaults",++ "chat.dm_settings",++ "chat.presence_prefs"++ ],"notes": "Wave 6: Live's chat (WebSocket, DMs, history, TTS, sounds, moderation) behind a service boundary on 127.0.0.1:4400, serving Live's chat paths since 2026-09-23. Since 2026-09-24 also its own site, openvibe.chat (global chat, messages, settings, sign-in with the Network), which works while Live is down.","health": "/health",-- "ready": "/ready"++ "ready": "/ready",++ "internalOrigin": "http://127.0.0.1:4400",++ "exposure": {++ "state": "live",++ "publicSite": "service",++ "note": "openvibe.chat since 2026-09-24 (global chat, messages, settings); also serves Live's chat paths"++ },++ "site": {++ "name": "Chat",++ "icon": "chat",++ "tagline": "One chat for the network",++ "what": "Global chat, rooms, stream chat and direct messages under one account, with or without JavaScript.",++ "legalProfile": "ugc",++ "position": 6++ }}@@@ -13,6 +13,19 @@"publicOrigin": "https://openvibe.codes","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4900",++ "exposure": {++ "state": "live",++ "publicSite": "service"++ },++ "site": {++ "name": "Codes",++ "icon": "codes",++ "tagline": "Build on OpenVibe",++ "what": "The developer portal: API and SDK docs, the service and capability registry, event schemas, credentials, playgrounds and mod publishing.",++ "legalProfile": "ugc",++ "position": 7++ },"capabilities": ["codes.release.read","codes.release.manage"@@@ -41,5 +41,18 @@"namespacesOwned": ["community.*"],-- "ready": "/api/ready"++ "ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4200",++ "exposure": {++ "state": "live",++ "publicSite": "service"++ },++ "site": {++ "name": "Community",++ "icon": "community",++ "tagline": "Pastes and posts",++ "what": "Share text and code with a link, comment, and follow what people on OpenVibe are making.",++ "legalProfile": "ugc",++ "position": 2++ }}@@@ -7,6 +7,13 @@"name": "OpenVibe.Contracts","status": "alpha","repository": "OpenVibers/OpenVibe.Contracts",++ "exposure": {++ "state": "library",++ "publicSite": null,++ "note": "schemas are also served by this registry at their $id URLs",++ "package": "openvibe-contracts",++ "repo": "OpenVibe.Contracts"++ },"domains": [],"capabilities": [],"eventsProduced": [],@@@ -13,6 +13,20 @@"publicOrigin": "https://openvibe.coupons","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4850",++ "exposure": {++ "state": "internal",++ "publicSite": "placeholder",++ "note": "the domain serves the OpenVibe.Sites placeholder page"++ },++ "site": {++ "name": "Coupons",++ "icon": "coupons",++ "tagline": "Codes that work",++ "what": "Coupon codes with merchant matching, restrictions, expiry and real-people validity reports.",++ "legalProfile": "info",++ "position": 17++ },"capabilities": ["coupons.coupon.submit","coupons.report.create",@@@ -13,6 +13,20 @@"publicOrigin": "https://openvibe.deals","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4840",++ "exposure": {++ "state": "internal",++ "publicSite": "placeholder",++ "note": "the domain serves the OpenVibe.Sites placeholder page"++ },++ "site": {++ "name": "Deals",++ "icon": "deals",++ "tagline": "Deals worth sharing",++ "what": "Deals submitted and voted on by the community, with source, price and freshness always shown.",++ "legalProfile": "info",++ "position": 16++ },"capabilities": ["deals.offer.submit","deals.offer.update",@@@ -13,6 +13,11 @@"publicOrigin": "https://events.openvibe.network","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4300",++ "exposure": {++ "state": "live",++ "publicSite": "service"++ },"capabilities": ["events.event.publish","events.event.read",@@@ -7,6 +7,11 @@"name": "OpenVibe.Examples","status": "alpha","repository": "OpenVibers/OpenVibe.Examples",++ "exposure": {++ "state": "repository",++ "publicSite": null,++ "note": "example apps with CI in OpenVibers/OpenVibe.Examples; not published as a package"++ },"domains": [],"capabilities": [],"eventsProduced": [],@@@ -7,9 +7,26 @@"name": "OpenVibe.Games","status": "stable","repository": "OpenVibers/OpenVibe.Games",-- "domains": ["openvibe.games", "play.openvibe.games"],++ "domains": [++ "openvibe.games",++ "play.openvibe.games"++ ],"publicOrigin": "https://openvibe.games","health": "/healthz",++ "ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:8000",++ "exposure": {++ "state": "live",++ "publicSite": "service"++ },++ "site": {++ "name": "Games",++ "icon": "games",++ "tagline": "Browser games",++ "what": "Scraplandia, a shared pixel canvas and other games that run in a tab and know your OpenVibe account.",++ "legalProfile": "games",++ "position": 3++ },"capabilities": ["games.mod.manage","games.mod.read",@@@ -28,7 +45,11 @@"games.mod.grants_changed","games.mod.revoked"],-- "eventsConsumed": ["network.user.token_valid_after"],-- "namespacesOwned": ["games.*"],++ "eventsConsumed": [++ "network.user.token_valid_after"++ ],++ "namespacesOwned": [++ "games.*"++ ],"notes": "Scraplandia (browser runtime). Players are keyed by canonical Network subjects (ADR-0006 in the repo). Calls Events (events.event.publish), Media (media.object.upload, namespace games: map-editor assets) and Network identity (identity.subject.resolve, batch legacy adoption) with the games client-credentials principal. Mods: games-content@1 declarative packs only; executable mods wait for OpenVibe.Host Stage C. No achievement system exists, so there are no games.achievement.* events."}@@@ -13,6 +13,20 @@"publicOrigin": "https://openvibe.host","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4910",++ "exposure": {++ "state": "internal",++ "publicSite": "placeholder",++ "note": "the domain serves the OpenVibe.Sites placeholder page"++ },++ "site": {++ "name": "Host",++ "icon": "host",++ "tagline": "Deploy and host",++ "what": "The network's deployment plane first, then simple hosting for community sites, bots and mods.",++ "legalProfile": "hosting",++ "position": 15++ },"capabilities": ["host.site.manage","host.deploy.create",@@@ -14,6 +14,19 @@"publicOrigin": "https://openvibe.live","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:3000",++ "exposure": {++ "state": "live",++ "publicSite": "service"++ },++ "site": {++ "name": "Live",++ "icon": "live",++ "tagline": "Streams, clips and chat",++ "what": "Live streaming from a browser, OBS or a phone, with restreaming, clips, VODs, chat games and channel points.",++ "legalProfile": "streaming",++ "position": 0++ },"capabilities": ["live.chat_context.read","live.chat_effects.write",@@@ -12,6 +12,20 @@],"publicOrigin": "https://openvibe.media","health": "/healthz",++ "ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4100",++ "exposure": {++ "state": "live",++ "publicSite": "service"++ },++ "site": {++ "name": "Media",++ "icon": "media",++ "tagline": "VODs, clips, files",++ "what": "The public media library behind every site: recorded streams, clips, images and uploads.",++ "legalProfile": "hosting",++ "position": 4++ },"capabilities": ["media.object.upload","media.object.read",@@@ -17,6 +17,20 @@],"publicOrigin": "https://openvibe.network","health": "/api/health",++ "ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4000",++ "exposure": {++ "state": "live",++ "publicSite": "service"++ },++ "site": {++ "name": "Network",++ "icon": "network",++ "tagline": "Account and themes",++ "what": "The account behind every site: one sign-in, themes, notifications, history and linked services.",++ "legalProfile": "account",++ "position": 5++ },"capabilities": ["identity.subject.resolve","mods.grant.manage",@@@ -13,6 +13,20 @@"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++ },"capabilities": ["news.story.create","news.story.read",@@@ -14,6 +14,20 @@"publicOrigin": "https://openre.stream","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4500",++ "exposure": {++ "state": "internal",++ "publicSite": "placeholder",++ "note": "the domain serves the OpenVibe.Sites placeholder page"++ },++ "site": {++ "name": "OpenRe.Stream",++ "icon": "stream",++ "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++ },"capabilities": ["openre.stream.read","openre.stream.write",@@@ -4,6 +4,13 @@"name": "OpenVibe.Publishing","status": "alpha","repository": "OpenVibers/OpenVibe.Publishing",++ "exposure": {++ "state": "library",++ "publicSite": null,++ "note": "the shared publishing runtime used by Wiki, Blog, News, Reviews, Deals, Coupons and Trade",++ "package": "openvibe-publishing",++ "repo": "OpenVibe.Publishing"++ },"domains": [],"capabilities": [],"eventsProduced": [],@@@ -7,6 +7,10 @@"name": "OpenVibe.Realtime","status": "retired","repository": "OpenVibers/OpenVibe.Realtime",++ "exposure": {++ "state": "placeholder",++ "publicSite": null++ },"domains": [],"capabilities": [],"eventsProduced": [],@@@ -13,6 +13,20 @@"publicOrigin": "https://openvibe.reviews","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4830",++ "exposure": {++ "state": "internal",++ "publicSite": "placeholder",++ "note": "the domain serves the OpenVibe.Sites placeholder page"++ },++ "site": {++ "name": "Reviews",++ "icon": "reviews",++ "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++ },"capabilities": ["reviews.entity.resolve","reviews.entity.manage",@@@ -4,6 +4,12 @@"name": "OpenVibe.SDK","status": "alpha","repository": "OpenVibers/OpenVibe.SDK",++ "exposure": {++ "state": "library",++ "publicSite": null,++ "package": "openvibe-sdk",++ "repo": "OpenVibe.SDK"++ },"domains": [],"capabilities": [],"eventsProduced": [],@@@ -13,6 +13,12 @@"publicOrigin": "https://search.openvibe.network","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4710",++ "exposure": {++ "state": "live",++ "publicSite": "service",++ "note": "public search page and query API at search.openvibe.network (since 2026-09-23)"++ },"capabilities": ["search.document.write","search.query.delegate",@@@ -4,6 +4,13 @@"name": "OpenVibe.Shared","status": "alpha","repository": "OpenVibers/OpenVibe.Shared",++ "exposure": {++ "state": "library",++ "publicSite": null,++ "note": "also served at /shared/* by each site",++ "package": "openvibe-shared",++ "repo": "OpenVibe.Shared"++ },"domains": [],"capabilities": [],"eventsProduced": [],@@@ -7,6 +7,11 @@"name": "OpenVibe.Sites","status": "stable","repository": "OpenVibers/OpenVibe.Sites",++ "exposure": {++ "state": "live",++ "publicSite": "service",++ "note": "serves the placeholder pages of domains whose service is not public yet"++ },"domains": [],"capabilities": [],"eventsProduced": [],@@@ -13,6 +13,12 @@"publicOrigin": "https://sources.openvibe.network","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4720",++ "exposure": {++ "state": "internal",++ "publicSite": null,++ "note": "internal ingestion service; sources.openvibe.network says so (its API is loopback-only)"++ },"capabilities": ["sources.source.read","sources.item.read",@@@ -13,6 +13,20 @@"publicOrigin": "https://openvibe.tips","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4610",++ "exposure": {++ "state": "internal",++ "publicSite": "placeholder",++ "note": "the domain serves the OpenVibe.Sites placeholder page"++ },++ "site": {++ "name": "Tips",++ "icon": "tips",++ "tagline": "Support creators",++ "what": "Tips, goals, paid messages and alerts for creators, settled by the network's billing ledger.",++ "legalProfile": "streaming",++ "position": 12++ },"capabilities": ["tips.profile.get","tips.profile.update",@@@ -13,6 +13,19 @@"publicOrigin": "https://openvibe.tools","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4001",++ "exposure": {++ "state": "live",++ "publicSite": "service"++ },++ "site": {++ "name": "Tools",++ "icon": "tools",++ "tagline": "Every online tool",++ "what": "More than 160 online tools: converters, downloaders, PDF and image tools, developer utilities and network diagnostics, each on its own address.",++ "legalProfile": "tools",++ "position": 1++ },"capabilities": ["tools.job.create","tools.job.read",@@@ -13,6 +13,20 @@"publicOrigin": "https://openvibe.trade","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4860",++ "exposure": {++ "state": "internal",++ "publicSite": "placeholder",++ "note": "the domain serves the OpenVibe.Sites placeholder page"++ },++ "site": {++ "name": "Trade",++ "icon": "trade",++ "tagline": "Watchlists and alerts",++ "what": "Informational watchlists, sourced market context and alerts. No custody, no order execution.",++ "legalProfile": "ugc",++ "position": 14++ },"capabilities": ["trade.instrument.resolve","trade.instrument.manage",@@@ -13,6 +13,20 @@"publicOrigin": "https://openvibe.vip","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4620",++ "exposure": {++ "state": "internal",++ "publicSite": "placeholder",++ "note": "the domain serves the OpenVibe.Sites placeholder page"++ },++ "site": {++ "name": "VIP",++ "icon": "vip",++ "tagline": "Memberships and perks",++ "what": "Creator and network memberships: plans, perks and benefits recognised on every OpenVibe site.",++ "legalProfile": "account",++ "position": 13++ },"capabilities": ["vip.plan.create","vip.plan.update",@@@ -13,6 +13,19 @@"publicOrigin": "https://openvibe.wiki","health": "/api/health","ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:4800",++ "exposure": {++ "state": "live",++ "publicSite": "service"++ },++ "site": {++ "name": "Wiki",++ "icon": "wiki",++ "tagline": "Knowledge, with sources",++ "what": "Wiki spaces with page trees, revision history, citations and discussion, editable together.",++ "legalProfile": "ugc",++ "position": 9++ },"capabilities": ["wiki.space.create","wiki.page.create",@@@ -1,12 +1,12 @@{"name": "openvibe-contracts",-- "version": "0.41.1",++ "version": "0.42.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-contracts",-- "version": "0.41.1",++ "version": "0.42.0","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.41.1",++ "version": "0.42.0","description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.","main": "index.js","types": "generated/typescript/index.d.ts",@@@ -546,6 +546,28 @@ client.invalidate(); await client.getToken(); ok(calls === 2, 'invalidate forcesconst failing = serviceAuth.createTokenClient({ tokenUrl: 'x', clientId: 'live', clientSecret: 'bad', audience: 'a', fetchImpl: async () => ({ ok: false, status: 401, json: async () => ({ error: 'invalid_client' }) }) });await assert.rejects(failing.getToken(), /401: invalid_client/);++// ── Manifest-derived registry (WS-C task 1): addresses, exposure, sites ──++{++ const ms = services.manifests;++ const ports = new Map();++ for (const m of ms) {++ ok(m.exposure && typeof m.exposure.state === 'string', `${m.id} says where it can be reached (exposure)`);++ const runs = ['live', 'internal'].includes(m.exposure.state) && !!m.ready;++ if (runs) ok(typeof m.internalOrigin === 'string', `${m.id} runs: it names its loopback address (internalOrigin)`);++ if (m.internalOrigin) {++ const port = new URL(m.internalOrigin).port;++ ok(!ports.has(port), `${m.id} internalOrigin port ${port} is its own${ports.has(port) ? ` (also ${ports.get(port)})` : ''}`);++ ports.set(port, m.id);++ }++ if (m.exposure.state === 'library') ok(m.exposure.package && m.exposure.repo, `${m.id} library names its package and repository`);++ if (m.exposure.state === 'live' && m.exposure.publicSite === 'service') ok(!!m.publicOrigin || (m.domains || []).length > 0 || !!m.exposure.note, `${m.id} is live: it has a public origin, its domains, or a note saying where`);++ if (m.site) ok(!!(m.publicOrigin || m.site.host), `${m.id} site has a host`);++ }++ const positions = ms.filter(m => m.site).map(m => m.site.position);++ ok(new Set(positions).size === positions.length, 'site positions are unique');++ ok(ms.filter(m => m.site).length >= 10, 'the network\'s sites are in the manifests');++}++// ── User-module namespaces ────────────────────────────────────────────────{const { modules } = contracts;
@@ -4,6 +4,17 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -4,6 +4,17 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
the notes were in the tag and commit messages (`git tag -n1`).
+## 0.42.0 — 2026-09-25
+
+The service manifests carry what Network's registry and site list used to hard-code (roadmap WS-C task 1, "manifest-derived registry"):
+
+- **`internalOrigin`**: the loopback address a running service answers on, which the registry polls. An `OV_<ID>_INTERNAL_URL` loopback override can still replace it.
+- **`exposure`**: where the service can be reached today. `state` is live, internal, library, repository, placeholder or retired; `publicSite` says what the public domain answers (the service, a placeholder, or nothing); there is an optional `note`; libraries also give `package` and `repo`. This is separate from `status` (maturity).
+- **`site`**: how a site is presented, with `name`, `icon`, `tagline`, `what`, `legalProfile` (the legal wording of /terms, /privacy, /dmca), `position`, and `host` for a site without a public origin. A service without `site` is not a site.
+- **`ready`** is added to the network, media and games manifests (`/api/ready`).
+
+The values are exactly what Network hard-coded. A service going public is now one manifest edit here. The test requires every first-party manifest to have an exposure, every running service to have a unique loopback port, libraries to name their package and repository, and site positions to be unique. The additions are optional in the schema, so this release is additive.
+
## 0.41.1 — 2026-09-25
## 0.41.1 — 2026-09-25
Plain-language descriptions for `ai.preferences`, `chat.preferences`, `games.progress.summary` and `live.profile`. my.openvibe.network now shows these descriptions to people on its AI & Data tab. No schema change.
Plain-language descriptions for `ai.preferences`, `chat.preferences`, `games.progress.summary` and `live.profile`. my.openvibe.network now shows these descriptions to people on its AI & Data tab. No schema change.
@@ -4,23 +4,194 @@
@@ -4,23 +4,194 @@
"title": "ServiceManifest",
"title": "ServiceManifest",
"description": "What a service is, where it lives and what it offers. The ecosystem registry is built from these, not from hard-coded route maps.",
"description": "What a service is, where it lives and what it offers. The ecosystem registry is built from these, not from hard-coded route maps.",
"type": "object",
"type": "object",
- "required": ["id", "version", "status", "repository", "domains", "capabilities", "eventsProduced", "eventsConsumed", "namespacesOwned"],
+ "required": [
+ "id",
+ "version",
+ "status",
+ "repository",
+ "domains",
+ "capabilities",
+ "eventsProduced",
+ "eventsConsumed",
+ "namespacesOwned"
+ ],
"additionalProperties": false,
"additionalProperties": false,
"properties": {
"properties": {
- "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$" },
- "name": { "type": "string" },
- "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?$" },
- "status": { "enum": ["placeholder", "alpha", "beta", "stable", "degraded", "retired"] },
- "repository": { "type": "string", "pattern": "^OpenVibers/[A-Za-z0-9._-]+$" },
- "domains": { "type": "array", "items": { "type": "string" } },
- "publicOrigin": { "type": "string", "format": "uri" },
- "health": { "type": "string", "pattern": "^/" },
- "ready": { "type": "string", "pattern": "^/" },
- "capabilities": { "type": "array", "items": { "type": "string" } },
- "eventsProduced": { "type": "array", "items": { "type": "string" } },
- "eventsConsumed": { "type": "array", "items": { "type": "string" } },
- "namespacesOwned": { "type": "array", "items": { "type": "string" } },
- "contractRanges": { "type": "object", "additionalProperties": { "type": "string" } },
- "notes": { "type": "string" }
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,39}$"
+ },
+ "name": {
+ "type": "string"
+ },
+ "version": {
+ "type": "string",
+ "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?$"
+ },
+ "status": {
+ "enum": [
+ "placeholder",
+ "alpha",
+ "beta",
+ "stable",
+ "degraded",
+ "retired"
+ ]
+ },
+ "repository": {
+ "type": "string",
+ "pattern": "^OpenVibers/[A-Za-z0-9._-]+$"
+ },
+ "domains": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "publicOrigin": {
+ "type": "string",
+ "format": "uri"
+ },
+ "health": {
+ "type": "string",
+ "pattern": "^/"
+ },
+ "ready": {
+ "type": "string",
+ "pattern": "^/"
+ },
+ "internalOrigin": {
+ "type": "string",
+ "pattern": "^http://(127\\.0\\.0\\.1|localhost|\\[::1\\]):[0-9]{2,5}$",
+ "description": "Where the service answers on its host (loopback only): the address the registry polls health and readiness from. A host inventory or an OV_<ID>_INTERNAL_URL loopback override may replace it; a service with nothing to run has none."
+ },
+ "exposure": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "state"
+ ],
+ "description": "Where the service can actually be reached today, which is separate from its maturity (status): live (its public domain serves it), internal (loopback only; the domain, if any, still serves a placeholder), library (a released package), repository (code with CI, nothing released or run), placeholder (charter only) or retired. When a service goes public, this changes in the same release that points its domain at it.",
+ "properties": {
+ "state": {
+ "enum": [
+ "live",
+ "internal",
+ "library",
+ "repository",
+ "placeholder",
+ "retired"
+ ]
+ },
+ "publicSite": {
+ "enum": [
+ "service",
+ "placeholder",
+ null
+ ],
+ "description": "What the public domain answers today: the service itself, a placeholder page, or nothing (no public domain)."
+ },
+ "note": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "package": {
+ "type": "string",
+ "pattern": "^[a-z0-9@/._-]+$",
+ "description": "For a library: the npm package name."
+ },
+ "repo": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9._-]+$",
+ "description": "For a library: the OpenVibers repository its release tags live in."
+ }
+ }
+ },
+ "site": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "name",
+ "icon",
+ "tagline",
+ "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.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "maxLength": 40,
+ "description": "Short name after 'OpenVibe.' (or a full name containing a dot)."
+ },
+ "icon": {
+ "type": "string",
+ "pattern": "^[a-z0-9-]+$"
+ },
+ "tagline": {
+ "type": "string",
+ "maxLength": 60
+ },
+ "what": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "legalProfile": {
+ "enum": [
+ "streaming",
+ "tools",
+ "ugc",
+ "games",
+ "hosting",
+ "account",
+ "info"
+ ],
+ "description": "Which legal wording its /terms, /privacy and /dmca use (openvibe-shared/legal)."
+ },
+ "position": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Order in site lists."
+ },
+ "host": {
+ "type": "string",
+ "pattern": "^[a-z0-9.-]+$",
+ "description": "Only while the manifest has no publicOrigin."
+ }
+ }
+ },
+ "capabilities": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "eventsProduced": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "eventsConsumed": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "namespacesOwned": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "contractRanges": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "notes": {
+ "type": "string"
+ }
}
}
}
}
@@ -453,6 +453,106 @@
@@ -453,6 +453,106 @@
"type": "string",
"type": "string",
"pattern": "^/"
"pattern": "^/"
},
},
+ "internalOrigin": {
+ "type": "string",
+ "pattern": "^http://(127\\.0\\.0\\.1|localhost|\\[::1\\]):[0-9]{2,5}$",
+ "description": "Where the service answers on its host (loopback only): the address the registry polls health and readiness from. A host inventory or an OV_<ID>_INTERNAL_URL loopback override may replace it; a service with nothing to run has none."
+ },
+ "exposure": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "state"
+ ],
+ "description": "Where the service can actually be reached today, which is separate from its maturity (status): live (its public domain serves it), internal (loopback only; the domain, if any, still serves a placeholder), library (a released package), repository (code with CI, nothing released or run), placeholder (charter only) or retired. When a service goes public, this changes in the same release that points its domain at it.",
+ "properties": {
+ "state": {
+ "enum": [
+ "live",
+ "internal",
+ "library",
+ "repository",
+ "placeholder",
+ "retired"
+ ]
+ },
+ "publicSite": {
+ "enum": [
+ "service",
+ "placeholder",
+ null
+ ],
+ "description": "What the public domain answers today: the service itself, a placeholder page, or nothing (no public domain)."
+ },
+ "note": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "package": {
+ "type": "string",
+ "pattern": "^[a-z0-9@/._-]+$",
+ "description": "For a library: the npm package name."
+ },
+ "repo": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9._-]+$",
+ "description": "For a library: the OpenVibers repository its release tags live in."
+ }
+ }
+ },
+ "site": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "name",
+ "icon",
+ "tagline",
+ "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.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "maxLength": 40,
+ "description": "Short name after 'OpenVibe.' (or a full name containing a dot)."
+ },
+ "icon": {
+ "type": "string",
+ "pattern": "^[a-z0-9-]+$"
+ },
+ "tagline": {
+ "type": "string",
+ "maxLength": 60
+ },
+ "what": {
+ "type": "string",
+ "maxLength": 300
+ },
+ "legalProfile": {
+ "enum": [
+ "streaming",
+ "tools",
+ "ugc",
+ "games",
+ "hosting",
+ "account",
+ "info"
+ ],
+ "description": "Which legal wording its /terms, /privacy and /dmca use (openvibe-shared/legal)."
+ },
+ "position": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Order in site lists."
+ },
+ "host": {
+ "type": "string",
+ "pattern": "^[a-z0-9.-]+$",
+ "description": "Only while the manifest has no publicOrigin."
+ }
+ }
+ },
"capabilities": {
"capabilities": {
"type": "array",
"type": "array",
"items": {
"items": {
@@ -143,6 +143,53 @@ export interface ServiceManifest {
@@ -143,6 +143,53 @@ export interface ServiceManifest {
publicOrigin?: string;
publicOrigin?: string;
health?: string;
health?: string;
ready?: string;
ready?: string;
+ /**
+ * Where the service answers on its host (loopback only): the address the registry polls health and readiness from. A host inventory or an OV_<ID>_INTERNAL_URL loopback override may replace it; a service with nothing to run has none.
+ */
+ internalOrigin?: string;
+ /**
+ * Where the service can actually be reached today, which is separate from its maturity (status): live (its public domain serves it), internal (loopback only; the domain, if any, still serves a placeholder), library (a released package), repository (code with CI, nothing released or run), placeholder (charter only) or retired. When a service goes public, this changes in the same release that points its domain at it.
+ */
+ exposure?: {
+ state: "live" | "internal" | "library" | "repository" | "placeholder" | "retired";
+ /**
+ * What the public domain answers today: the service itself, a placeholder page, or nothing (no public domain).
+ */
+ publicSite?: "service" | "placeholder" | null;
+ note?: string;
+ /**
+ * For a library: the npm package name.
+ */
+ package?: string;
+ /**
+ * For a library: the OpenVibers repository its release tags live in.
+ */
+ 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.
+ */
+ site?: {
+ /**
+ * Short name after 'OpenVibe.' (or a full name containing a dot).
+ */
+ name: string;
+ icon: string;
+ tagline: string;
+ what: string;
+ /**
+ * Which legal wording its /terms, /privacy and /dmca use (openvibe-shared/legal).
+ */
+ legalProfile: "streaming" | "tools" | "ugc" | "games" | "hosting" | "account" | "info";
+ /**
+ * Order in site lists.
+ */
+ position?: number;
+ /**
+ * Only while the manifest has no publicOrigin.
+ */
+ host?: string;
+ };
capabilities: string[];
capabilities: string[];
eventsProduced: string[];
eventsProduced: string[];
eventsConsumed: string[];
eventsConsumed: string[];
@@ -13,6 +13,12 @@
@@ -13,6 +13,12 @@
"publicOrigin": "https://ai.openvibe.network",
"publicOrigin": "https://ai.openvibe.network",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4700",
+ "exposure": {
+ "state": "internal",
+ "publicSite": null,
+ "note": "called by other services at 127.0.0.1:4700; ai.openvibe.network serves the OpenVibe.Sites placeholder page"
+ },
"capabilities": [
"capabilities": [
"ai.run.create",
"ai.run.create",
"ai.run.read",
"ai.run.read",
@@ -27,6 +33,8 @@
@@ -27,6 +33,8 @@
"ai.run.cached"
"ai.run.cached"
],
],
"eventsConsumed": [],
"eventsConsumed": [],
- "namespacesOwned": ["ai.usage_summary"],
+ "namespacesOwned": [
+ "ai.usage_summary"
+ ],
"notes": "Providers, models, routing profiles, versioned prompt templates and workflows, runs, request logs, citations, scoped cache, quotas and audit. Workflow output is a draft/evidence package attributed to workflow + model + run. ai.run.queued|succeeded|failed|cached have payload contracts (v0.30.0, status planned): AI publishes them once its Events client is wired in; it publishes none today. ai.run.create takes ai.run-request@1 and returns ai.run@1."
"notes": "Providers, models, routing profiles, versioned prompt templates and workflows, runs, request logs, citations, scoped cache, quotas and audit. Workflow output is a draft/evidence package attributed to workflow + model + run. ai.run.queued|succeeded|failed|cached have payload contracts (v0.30.0, status planned): AI publishes them once its Events client is wired in; it publishes none today. ai.run.create takes ai.run-request@1 and returns ai.run@1."
}
}
@@ -13,6 +13,12 @@
@@ -13,6 +13,12 @@
"publicOrigin": "https://billing.openvibe.network",
"publicOrigin": "https://billing.openvibe.network",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4600",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service",
+ "note": "the public domain is the staff console; accounts pay through each product"
+ },
"capabilities": [
"capabilities": [
"billing.intent.create",
"billing.intent.create",
"billing.transfer.create",
"billing.transfer.create",
@@ -13,6 +13,19 @@
@@ -13,6 +13,19 @@
"publicOrigin": "https://openvibe.blog",
"publicOrigin": "https://openvibe.blog",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4810",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service"
+ },
+ "site": {
+ "name": "Blog",
+ "icon": "blog",
+ "tagline": "Long-form, yours to keep",
+ "what": "The official OpenVibe blog and a blog for every member: drafts, scheduling, media, comments and feeds.",
+ "legalProfile": "info",
+ "position": 8
+ },
"capabilities": [
"capabilities": [
"blog.blog.create",
"blog.blog.create",
"blog.blog.configure",
"blog.blog.configure",
@@ -7,7 +7,9 @@
@@ -7,7 +7,9 @@
"name": "OpenVibe.Chat",
"name": "OpenVibe.Chat",
"status": "alpha",
"status": "alpha",
"repository": "OpenVibers/OpenVibe.Chat",
"repository": "OpenVibers/OpenVibe.Chat",
- "domains": ["openvibe.chat"],
+ "domains": [
+ "openvibe.chat"
+ ],
"publicOrigin": "https://openvibe.chat",
"publicOrigin": "https://openvibe.chat",
"capabilities": [
"capabilities": [
"chat.live_bridge.write",
"chat.live_bridge.write",
@@ -23,8 +25,27 @@
@@ -23,8 +25,27 @@
"eventsConsumed": [
"eventsConsumed": [
"network.user.token_valid_after"
"network.user.token_valid_after"
],
],
- "namespacesOwned": ["chat.preferences", "chat.tts_defaults", "chat.dm_settings", "chat.presence_prefs"],
+ "namespacesOwned": [
+ "chat.preferences",
+ "chat.tts_defaults",
+ "chat.dm_settings",
+ "chat.presence_prefs"
+ ],
"notes": "Wave 6: Live's chat (WebSocket, DMs, history, TTS, sounds, moderation) behind a service boundary on 127.0.0.1:4400, serving Live's chat paths since 2026-09-23. Since 2026-09-24 also its own site, openvibe.chat (global chat, messages, settings, sign-in with the Network), which works while Live is down.",
"notes": "Wave 6: Live's chat (WebSocket, DMs, history, TTS, sounds, moderation) behind a service boundary on 127.0.0.1:4400, serving Live's chat paths since 2026-09-23. Since 2026-09-24 also its own site, openvibe.chat (global chat, messages, settings, sign-in with the Network), which works while Live is down.",
"health": "/health",
"health": "/health",
- "ready": "/ready"
+ "ready": "/ready",
+ "internalOrigin": "http://127.0.0.1:4400",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service",
+ "note": "openvibe.chat since 2026-09-24 (global chat, messages, settings); also serves Live's chat paths"
+ },
+ "site": {
+ "name": "Chat",
+ "icon": "chat",
+ "tagline": "One chat for the network",
+ "what": "Global chat, rooms, stream chat and direct messages under one account, with or without JavaScript.",
+ "legalProfile": "ugc",
+ "position": 6
+ }
}
}
@@ -13,6 +13,19 @@
@@ -13,6 +13,19 @@
"publicOrigin": "https://openvibe.codes",
"publicOrigin": "https://openvibe.codes",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4900",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service"
+ },
+ "site": {
+ "name": "Codes",
+ "icon": "codes",
+ "tagline": "Build on OpenVibe",
+ "what": "The developer portal: API and SDK docs, the service and capability registry, event schemas, credentials, playgrounds and mod publishing.",
+ "legalProfile": "ugc",
+ "position": 7
+ },
"capabilities": [
"capabilities": [
"codes.release.read",
"codes.release.read",
"codes.release.manage"
"codes.release.manage"
@@ -41,5 +41,18 @@
@@ -41,5 +41,18 @@
"namespacesOwned": [
"namespacesOwned": [
"community.*"
"community.*"
],
],
- "ready": "/api/ready"
+ "ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4200",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service"
+ },
+ "site": {
+ "name": "Community",
+ "icon": "community",
+ "tagline": "Pastes and posts",
+ "what": "Share text and code with a link, comment, and follow what people on OpenVibe are making.",
+ "legalProfile": "ugc",
+ "position": 2
+ }
}
}
@@ -7,6 +7,13 @@
@@ -7,6 +7,13 @@
"name": "OpenVibe.Contracts",
"name": "OpenVibe.Contracts",
"status": "alpha",
"status": "alpha",
"repository": "OpenVibers/OpenVibe.Contracts",
"repository": "OpenVibers/OpenVibe.Contracts",
+ "exposure": {
+ "state": "library",
+ "publicSite": null,
+ "note": "schemas are also served by this registry at their $id URLs",
+ "package": "openvibe-contracts",
+ "repo": "OpenVibe.Contracts"
+ },
"domains": [],
"domains": [],
"capabilities": [],
"capabilities": [],
"eventsProduced": [],
"eventsProduced": [],
@@ -13,6 +13,20 @@
@@ -13,6 +13,20 @@
"publicOrigin": "https://openvibe.coupons",
"publicOrigin": "https://openvibe.coupons",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4850",
+ "exposure": {
+ "state": "internal",
+ "publicSite": "placeholder",
+ "note": "the domain serves the OpenVibe.Sites placeholder page"
+ },
+ "site": {
+ "name": "Coupons",
+ "icon": "coupons",
+ "tagline": "Codes that work",
+ "what": "Coupon codes with merchant matching, restrictions, expiry and real-people validity reports.",
+ "legalProfile": "info",
+ "position": 17
+ },
"capabilities": [
"capabilities": [
"coupons.coupon.submit",
"coupons.coupon.submit",
"coupons.report.create",
"coupons.report.create",
@@ -13,6 +13,20 @@
@@ -13,6 +13,20 @@
"publicOrigin": "https://openvibe.deals",
"publicOrigin": "https://openvibe.deals",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4840",
+ "exposure": {
+ "state": "internal",
+ "publicSite": "placeholder",
+ "note": "the domain serves the OpenVibe.Sites placeholder page"
+ },
+ "site": {
+ "name": "Deals",
+ "icon": "deals",
+ "tagline": "Deals worth sharing",
+ "what": "Deals submitted and voted on by the community, with source, price and freshness always shown.",
+ "legalProfile": "info",
+ "position": 16
+ },
"capabilities": [
"capabilities": [
"deals.offer.submit",
"deals.offer.submit",
"deals.offer.update",
"deals.offer.update",
@@ -13,6 +13,11 @@
@@ -13,6 +13,11 @@
"publicOrigin": "https://events.openvibe.network",
"publicOrigin": "https://events.openvibe.network",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4300",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service"
+ },
"capabilities": [
"capabilities": [
"events.event.publish",
"events.event.publish",
"events.event.read",
"events.event.read",
@@ -7,6 +7,11 @@
@@ -7,6 +7,11 @@
"name": "OpenVibe.Examples",
"name": "OpenVibe.Examples",
"status": "alpha",
"status": "alpha",
"repository": "OpenVibers/OpenVibe.Examples",
"repository": "OpenVibers/OpenVibe.Examples",
+ "exposure": {
+ "state": "repository",
+ "publicSite": null,
+ "note": "example apps with CI in OpenVibers/OpenVibe.Examples; not published as a package"
+ },
"domains": [],
"domains": [],
"capabilities": [],
"capabilities": [],
"eventsProduced": [],
"eventsProduced": [],
@@ -7,9 +7,26 @@
@@ -7,9 +7,26 @@
"name": "OpenVibe.Games",
"name": "OpenVibe.Games",
"status": "stable",
"status": "stable",
"repository": "OpenVibers/OpenVibe.Games",
"repository": "OpenVibers/OpenVibe.Games",
- "domains": ["openvibe.games", "play.openvibe.games"],
+ "domains": [
+ "openvibe.games",
+ "play.openvibe.games"
+ ],
"publicOrigin": "https://openvibe.games",
"publicOrigin": "https://openvibe.games",
"health": "/healthz",
"health": "/healthz",
+ "ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:8000",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service"
+ },
+ "site": {
+ "name": "Games",
+ "icon": "games",
+ "tagline": "Browser games",
+ "what": "Scraplandia, a shared pixel canvas and other games that run in a tab and know your OpenVibe account.",
+ "legalProfile": "games",
+ "position": 3
+ },
"capabilities": [
"capabilities": [
"games.mod.manage",
"games.mod.manage",
"games.mod.read",
"games.mod.read",
@@ -28,7 +45,11 @@
@@ -28,7 +45,11 @@
"games.mod.grants_changed",
"games.mod.grants_changed",
"games.mod.revoked"
"games.mod.revoked"
],
],
- "eventsConsumed": ["network.user.token_valid_after"],
- "namespacesOwned": ["games.*"],
+ "eventsConsumed": [
+ "network.user.token_valid_after"
+ ],
+ "namespacesOwned": [
+ "games.*"
+ ],
"notes": "Scraplandia (browser runtime). Players are keyed by canonical Network subjects (ADR-0006 in the repo). Calls Events (events.event.publish), Media (media.object.upload, namespace games: map-editor assets) and Network identity (identity.subject.resolve, batch legacy adoption) with the games client-credentials principal. Mods: games-content@1 declarative packs only; executable mods wait for OpenVibe.Host Stage C. No achievement system exists, so there are no games.achievement.* events."
"notes": "Scraplandia (browser runtime). Players are keyed by canonical Network subjects (ADR-0006 in the repo). Calls Events (events.event.publish), Media (media.object.upload, namespace games: map-editor assets) and Network identity (identity.subject.resolve, batch legacy adoption) with the games client-credentials principal. Mods: games-content@1 declarative packs only; executable mods wait for OpenVibe.Host Stage C. No achievement system exists, so there are no games.achievement.* events."
}
}
@@ -13,6 +13,20 @@
@@ -13,6 +13,20 @@
"publicOrigin": "https://openvibe.host",
"publicOrigin": "https://openvibe.host",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4910",
+ "exposure": {
+ "state": "internal",
+ "publicSite": "placeholder",
+ "note": "the domain serves the OpenVibe.Sites placeholder page"
+ },
+ "site": {
+ "name": "Host",
+ "icon": "host",
+ "tagline": "Deploy and host",
+ "what": "The network's deployment plane first, then simple hosting for community sites, bots and mods.",
+ "legalProfile": "hosting",
+ "position": 15
+ },
"capabilities": [
"capabilities": [
"host.site.manage",
"host.site.manage",
"host.deploy.create",
"host.deploy.create",
@@ -14,6 +14,19 @@
@@ -14,6 +14,19 @@
"publicOrigin": "https://openvibe.live",
"publicOrigin": "https://openvibe.live",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:3000",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service"
+ },
+ "site": {
+ "name": "Live",
+ "icon": "live",
+ "tagline": "Streams, clips and chat",
+ "what": "Live streaming from a browser, OBS or a phone, with restreaming, clips, VODs, chat games and channel points.",
+ "legalProfile": "streaming",
+ "position": 0
+ },
"capabilities": [
"capabilities": [
"live.chat_context.read",
"live.chat_context.read",
"live.chat_effects.write",
"live.chat_effects.write",
@@ -12,6 +12,20 @@
@@ -12,6 +12,20 @@
],
],
"publicOrigin": "https://openvibe.media",
"publicOrigin": "https://openvibe.media",
"health": "/healthz",
"health": "/healthz",
+ "ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4100",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service"
+ },
+ "site": {
+ "name": "Media",
+ "icon": "media",
+ "tagline": "VODs, clips, files",
+ "what": "The public media library behind every site: recorded streams, clips, images and uploads.",
+ "legalProfile": "hosting",
+ "position": 4
+ },
"capabilities": [
"capabilities": [
"media.object.upload",
"media.object.upload",
"media.object.read",
"media.object.read",
@@ -17,6 +17,20 @@
@@ -17,6 +17,20 @@
],
],
"publicOrigin": "https://openvibe.network",
"publicOrigin": "https://openvibe.network",
"health": "/api/health",
"health": "/api/health",
+ "ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4000",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service"
+ },
+ "site": {
+ "name": "Network",
+ "icon": "network",
+ "tagline": "Account and themes",
+ "what": "The account behind every site: one sign-in, themes, notifications, history and linked services.",
+ "legalProfile": "account",
+ "position": 5
+ },
"capabilities": [
"capabilities": [
"identity.subject.resolve",
"identity.subject.resolve",
"mods.grant.manage",
"mods.grant.manage",
@@ -13,6 +13,20 @@
@@ -13,6 +13,20 @@
"publicOrigin": "https://openvibe.news",
"publicOrigin": "https://openvibe.news",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"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
+ },
"capabilities": [
"capabilities": [
"news.story.create",
"news.story.create",
"news.story.read",
"news.story.read",
@@ -14,6 +14,20 @@
@@ -14,6 +14,20 @@
"publicOrigin": "https://openre.stream",
"publicOrigin": "https://openre.stream",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4500",
+ "exposure": {
+ "state": "internal",
+ "publicSite": "placeholder",
+ "note": "the domain serves the OpenVibe.Sites placeholder page"
+ },
+ "site": {
+ "name": "OpenRe.Stream",
+ "icon": "stream",
+ "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
+ },
"capabilities": [
"capabilities": [
"openre.stream.read",
"openre.stream.read",
"openre.stream.write",
"openre.stream.write",
@@ -4,6 +4,13 @@
@@ -4,6 +4,13 @@
"name": "OpenVibe.Publishing",
"name": "OpenVibe.Publishing",
"status": "alpha",
"status": "alpha",
"repository": "OpenVibers/OpenVibe.Publishing",
"repository": "OpenVibers/OpenVibe.Publishing",
+ "exposure": {
+ "state": "library",
+ "publicSite": null,
+ "note": "the shared publishing runtime used by Wiki, Blog, News, Reviews, Deals, Coupons and Trade",
+ "package": "openvibe-publishing",
+ "repo": "OpenVibe.Publishing"
+ },
"domains": [],
"domains": [],
"capabilities": [],
"capabilities": [],
"eventsProduced": [],
"eventsProduced": [],
@@ -7,6 +7,10 @@
@@ -7,6 +7,10 @@
"name": "OpenVibe.Realtime",
"name": "OpenVibe.Realtime",
"status": "retired",
"status": "retired",
"repository": "OpenVibers/OpenVibe.Realtime",
"repository": "OpenVibers/OpenVibe.Realtime",
+ "exposure": {
+ "state": "placeholder",
+ "publicSite": null
+ },
"domains": [],
"domains": [],
"capabilities": [],
"capabilities": [],
"eventsProduced": [],
"eventsProduced": [],
@@ -13,6 +13,20 @@
@@ -13,6 +13,20 @@
"publicOrigin": "https://openvibe.reviews",
"publicOrigin": "https://openvibe.reviews",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4830",
+ "exposure": {
+ "state": "internal",
+ "publicSite": "placeholder",
+ "note": "the domain serves the OpenVibe.Sites placeholder page"
+ },
+ "site": {
+ "name": "Reviews",
+ "icon": "reviews",
+ "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
+ },
"capabilities": [
"capabilities": [
"reviews.entity.resolve",
"reviews.entity.resolve",
"reviews.entity.manage",
"reviews.entity.manage",
@@ -4,6 +4,12 @@
@@ -4,6 +4,12 @@
"name": "OpenVibe.SDK",
"name": "OpenVibe.SDK",
"status": "alpha",
"status": "alpha",
"repository": "OpenVibers/OpenVibe.SDK",
"repository": "OpenVibers/OpenVibe.SDK",
+ "exposure": {
+ "state": "library",
+ "publicSite": null,
+ "package": "openvibe-sdk",
+ "repo": "OpenVibe.SDK"
+ },
"domains": [],
"domains": [],
"capabilities": [],
"capabilities": [],
"eventsProduced": [],
"eventsProduced": [],
@@ -13,6 +13,12 @@
@@ -13,6 +13,12 @@
"publicOrigin": "https://search.openvibe.network",
"publicOrigin": "https://search.openvibe.network",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4710",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service",
+ "note": "public search page and query API at search.openvibe.network (since 2026-09-23)"
+ },
"capabilities": [
"capabilities": [
"search.document.write",
"search.document.write",
"search.query.delegate",
"search.query.delegate",
@@ -4,6 +4,13 @@
@@ -4,6 +4,13 @@
"name": "OpenVibe.Shared",
"name": "OpenVibe.Shared",
"status": "alpha",
"status": "alpha",
"repository": "OpenVibers/OpenVibe.Shared",
"repository": "OpenVibers/OpenVibe.Shared",
+ "exposure": {
+ "state": "library",
+ "publicSite": null,
+ "note": "also served at /shared/* by each site",
+ "package": "openvibe-shared",
+ "repo": "OpenVibe.Shared"
+ },
"domains": [],
"domains": [],
"capabilities": [],
"capabilities": [],
"eventsProduced": [],
"eventsProduced": [],
@@ -7,6 +7,11 @@
@@ -7,6 +7,11 @@
"name": "OpenVibe.Sites",
"name": "OpenVibe.Sites",
"status": "stable",
"status": "stable",
"repository": "OpenVibers/OpenVibe.Sites",
"repository": "OpenVibers/OpenVibe.Sites",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service",
+ "note": "serves the placeholder pages of domains whose service is not public yet"
+ },
"domains": [],
"domains": [],
"capabilities": [],
"capabilities": [],
"eventsProduced": [],
"eventsProduced": [],
@@ -13,6 +13,12 @@
@@ -13,6 +13,12 @@
"publicOrigin": "https://sources.openvibe.network",
"publicOrigin": "https://sources.openvibe.network",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4720",
+ "exposure": {
+ "state": "internal",
+ "publicSite": null,
+ "note": "internal ingestion service; sources.openvibe.network says so (its API is loopback-only)"
+ },
"capabilities": [
"capabilities": [
"sources.source.read",
"sources.source.read",
"sources.item.read",
"sources.item.read",
@@ -13,6 +13,20 @@
@@ -13,6 +13,20 @@
"publicOrigin": "https://openvibe.tips",
"publicOrigin": "https://openvibe.tips",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4610",
+ "exposure": {
+ "state": "internal",
+ "publicSite": "placeholder",
+ "note": "the domain serves the OpenVibe.Sites placeholder page"
+ },
+ "site": {
+ "name": "Tips",
+ "icon": "tips",
+ "tagline": "Support creators",
+ "what": "Tips, goals, paid messages and alerts for creators, settled by the network's billing ledger.",
+ "legalProfile": "streaming",
+ "position": 12
+ },
"capabilities": [
"capabilities": [
"tips.profile.get",
"tips.profile.get",
"tips.profile.update",
"tips.profile.update",
@@ -13,6 +13,19 @@
@@ -13,6 +13,19 @@
"publicOrigin": "https://openvibe.tools",
"publicOrigin": "https://openvibe.tools",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4001",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service"
+ },
+ "site": {
+ "name": "Tools",
+ "icon": "tools",
+ "tagline": "Every online tool",
+ "what": "More than 160 online tools: converters, downloaders, PDF and image tools, developer utilities and network diagnostics, each on its own address.",
+ "legalProfile": "tools",
+ "position": 1
+ },
"capabilities": [
"capabilities": [
"tools.job.create",
"tools.job.create",
"tools.job.read",
"tools.job.read",
@@ -13,6 +13,20 @@
@@ -13,6 +13,20 @@
"publicOrigin": "https://openvibe.trade",
"publicOrigin": "https://openvibe.trade",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4860",
+ "exposure": {
+ "state": "internal",
+ "publicSite": "placeholder",
+ "note": "the domain serves the OpenVibe.Sites placeholder page"
+ },
+ "site": {
+ "name": "Trade",
+ "icon": "trade",
+ "tagline": "Watchlists and alerts",
+ "what": "Informational watchlists, sourced market context and alerts. No custody, no order execution.",
+ "legalProfile": "ugc",
+ "position": 14
+ },
"capabilities": [
"capabilities": [
"trade.instrument.resolve",
"trade.instrument.resolve",
"trade.instrument.manage",
"trade.instrument.manage",
@@ -13,6 +13,20 @@
@@ -13,6 +13,20 @@
"publicOrigin": "https://openvibe.vip",
"publicOrigin": "https://openvibe.vip",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4620",
+ "exposure": {
+ "state": "internal",
+ "publicSite": "placeholder",
+ "note": "the domain serves the OpenVibe.Sites placeholder page"
+ },
+ "site": {
+ "name": "VIP",
+ "icon": "vip",
+ "tagline": "Memberships and perks",
+ "what": "Creator and network memberships: plans, perks and benefits recognised on every OpenVibe site.",
+ "legalProfile": "account",
+ "position": 13
+ },
"capabilities": [
"capabilities": [
"vip.plan.create",
"vip.plan.create",
"vip.plan.update",
"vip.plan.update",
@@ -13,6 +13,19 @@
@@ -13,6 +13,19 @@
"publicOrigin": "https://openvibe.wiki",
"publicOrigin": "https://openvibe.wiki",
"health": "/api/health",
"health": "/api/health",
"ready": "/api/ready",
"ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:4800",
+ "exposure": {
+ "state": "live",
+ "publicSite": "service"
+ },
+ "site": {
+ "name": "Wiki",
+ "icon": "wiki",
+ "tagline": "Knowledge, with sources",
+ "what": "Wiki spaces with page trees, revision history, citations and discussion, editable together.",
+ "legalProfile": "ugc",
+ "position": 9
+ },
"capabilities": [
"capabilities": [
"wiki.space.create",
"wiki.space.create",
"wiki.page.create",
"wiki.page.create",
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.41.1",
+ "version": "0.42.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.41.1",
+ "version": "0.42.0",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.41.1",
+ "version": "0.42.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
"types": "generated/typescript/index.d.ts",
@@ -546,6 +546,28 @@ client.invalidate(); await client.getToken(); ok(calls === 2, 'invalidate forces
@@ -546,6 +546,28 @@ client.invalidate(); await client.getToken(); ok(calls === 2, 'invalidate forces
const failing = serviceAuth.createTokenClient({ tokenUrl: 'x', clientId: 'live', clientSecret: 'bad', audience: 'a', fetchImpl: async () => ({ ok: false, status: 401, json: async () => ({ error: 'invalid_client' }) }) });
const failing = serviceAuth.createTokenClient({ tokenUrl: 'x', clientId: 'live', clientSecret: 'bad', audience: 'a', fetchImpl: async () => ({ ok: false, status: 401, json: async () => ({ error: 'invalid_client' }) }) });
await assert.rejects(failing.getToken(), /401: invalid_client/);
await assert.rejects(failing.getToken(), /401: invalid_client/);
+// ── Manifest-derived registry (WS-C task 1): addresses, exposure, sites ──
+{
+ const ms = services.manifests;
+ const ports = new Map();
+ for (const m of ms) {
+ ok(m.exposure && typeof m.exposure.state === 'string', `${m.id} says where it can be reached (exposure)`);
+ const runs = ['live', 'internal'].includes(m.exposure.state) && !!m.ready;
+ if (runs) ok(typeof m.internalOrigin === 'string', `${m.id} runs: it names its loopback address (internalOrigin)`);
+ if (m.internalOrigin) {
+ const port = new URL(m.internalOrigin).port;
+ ok(!ports.has(port), `${m.id} internalOrigin port ${port} is its own${ports.has(port) ? ` (also ${ports.get(port)})` : ''}`);
+ ports.set(port, m.id);
+ }
+ if (m.exposure.state === 'library') ok(m.exposure.package && m.exposure.repo, `${m.id} library names its package and repository`);
+ if (m.exposure.state === 'live' && m.exposure.publicSite === 'service') ok(!!m.publicOrigin || (m.domains || []).length > 0 || !!m.exposure.note, `${m.id} is live: it has a public origin, its domains, or a note saying where`);
+ if (m.site) ok(!!(m.publicOrigin || m.site.host), `${m.id} site has a host`);
+ }
+ const positions = ms.filter(m => m.site).map(m => m.site.position);
+ ok(new Set(positions).size === positions.length, 'site positions are unique');
+ ok(ms.filter(m => m.site).length >= 10, 'the network\'s sites are in the manifests');
+}
+
// ── User-module namespaces ────────────────────────────────────────────────
// ── User-module namespaces ────────────────────────────────────────────────
{
{
const { modules } = contracts;
const { modules } = contracts;