openvibers/openvibe.contracts Commits
v0.34.0: CHANGELOG, README (147 schemas, 117 event payloads, 31 service manifests, 191 capabilities; realtime retired; the 29 listed events still without a payload contract; a Staff capability map section), STATUS.json date, version 0.34.0 in package.json and package-lock.json; the Deals manifest notes say OpenVibe.Network consumes deals.watch.matched (server/notifications/events-consumer.js), not that the consumer is unbuilt
Changes
@@@ -4,6 +4,151 @@ 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.34.0 — 2026-09-24++++Additive: `compat.js` reports no breaking change against v0.33.1. Closes the Contracts rows of the++roadmap completeness audit (§28.3, §30.2, W1 D7, W12, W22).++++**51 event payload contracts**, all `active`: every one is emitted by its producer's code today.++Each was read from the emitter and its call sites. News, Reviews and Deals were also checked against++envelopes captured from their own code, and Trade against its one production outbox row.++++- News (11): `news.source.ingested|failed`, `news.cluster.updated`,++ `news.story.created|flagged|published|updated|unpublished|retracted`, `news.index_document.*`.++- Reviews (9): `reviews.entity.merged|split`, `reviews.signal.added|removed`,++ `reviews.summary.published|updated|unpublished`, `reviews.index_document.*`.++- Coupons (8): `coupons.coupon.created|updated|expired|disabled`, `coupons.report.created`,++ `coupons.confidence.changed`, `coupons.index_document.*`.++- Deals (6): `deals.offer.created|updated|expired`, `deals.vote.changed`, `deals.index_document.*`.++- Trade (5): `trade.observation.created`, `trade.source.stale|recovered`, `trade.index_document.*`.++- Games (10): `games.player.joined|left`, `games.skill.leveled`, `games.blueprint.unlocked`,++ `games.world.saved`, `games.mod.installed|enabled|disabled|grants_changed|revoked`.++- Media (2): `media.object.deleted` and `media.object.visibility_changed` (OpenVibe.Media 039dc48).++ Triggers stage them in the transaction that changes the object, and the outbox writes them there.++ They carry identity and state only: never the title, owner, storage keys or size. A++ visibility change never repeats the value. The media manifest now lists both.++++Every `*.index_document.upserted` is a `search.index-document@1` with the owner fixed. Every++`.deleted` is a `{ type, id, revision }` tombstone.++++Community lists no `eventsProduced`, because it emits nothing yet, so there is no `community.*`++contract. 29 listed events still have no contract:++++- chat: 3++- codes: 3++- host: 4++- media: `vod.*`, `clip.*`, `object.uploaded` and `storage.*`, 7 in all++- network: 5++- openre: 7++++Found while reading the producers (fixes belong in those repos):++++- **News drops some flag events.** `stories.js:238` sends `priority: 'normal'` for `source_updated`++ flags. The envelope allows only `critical|important|low`, so Events refuses them.++- **Coupons can publish service notes.** A calling service's note is copied into the public event's++ `reason`.++- **Deals under-reports changes.** On the importer paths, `changed` can leave out a `product_id` or++ `expires_at` change.++++**ADR amendments** (dated 2026-09-24):++++- **ADR-003, delegated authorization.**++ - Who may name the acting person, and the rule that a call gets the intersection of the app's++ grants, the approved scopes and the person's own rights.++ - Staff power and money are never delegated. Tokens last 5 minutes and cannot be refreshed. Audit++ names both the app and the person.++ - The approval UX in force today, and what Network still owes: the capability list on the chooser,++ stored authorizations, and connected apps with revoke.++- **ADR-006:**++ - four placement classes: canonical, hot cache, asset origin, local scratch;++ - `local` as the first-class dev provider;++ - the infrequent-access tier deferred at 2,839 objects / 481 GB.++- **ADR-007:**++ - PgBouncer transaction pooling;++ - replicas only for reads that tolerate lag;++ - SERIALIZABLE plus ordered row locks and 40001 retries on money paths;++ - Redis never authoritative.++- **ADR-012:**++ - The roadmap's ledger names mapped to Billing's transaction types and legs: platform fee,++ creator earning, payout hold and release, compensating reversals.++ - OpenCoins stays in Network. This supersedes the Wave 8 line "Network's OpenCoins wallet becomes++ a Billing client".++ - Billing's `cashouts`, reversal transactions and VIP plans replace the roadmap's `payouts`,++ `refunds` and `plans` tables.++- **ADR-013:**++ - Package signing and review are named as open owner decisions.++ - Mod monetization goes only through Billing and VIP primitives.++ - The manifest 1.1.0 fields (below).++- **ADR-021, extraction decided.** There is no analytics service. Analytics stays per service through++ `openvibe-shared/analytics`, and the schema stays in Shared. The decision rests on measured volume:++ Live has 1.78M raw events in 30 days (58k a day, 502 MB). The ADR names the triggers for a revisit.++- **ADR-022, the Wave 12 revisit.** The decision stands. The ADR names the trigger for the next++ revisit.++++**Realtime is `retired`** (ADR-005), not a placeholder. A retired manifest offers no domain,++capability, event, namespace or health path.++++**Staff capability map** (W1 D7, ADR-022): a new contract, `policy.staff-role-map@1` (owner `network`), and the map++in force, `manifests/policy/staff-roles.json`.++++- **Roles:** `user < streamer < global_mod < admin < owner`. `owner` is never a stored role: it is++ `role: admin` plus the `is_owner` claim. `user` and `streamer` hold no staff capability.++- **32 `staff.<area>.<action>` capabilities**, each with the lowest role that holds it:++ - 12 for `global_mod`, including `staff.moderation.chat|calls|channels|bans|ip|logs|bypass|pastes|discussions`,++ `staff.content.view_private` and `staff.content.hide`;++ - 15 for `admin`, including `staff.users.manage`, `staff.roles.assign`, `staff.site.view|configure`++ and `staff.streams.end|manage`;++ - 5 for `owner`: `staff.roles.grant_admin`, `staff.secrets.manage`,++ `staff.money.freeze|cashouts` and `staff.loyalty.grant`.++++ Each lists the file:line role checks it replaces in Live (210 sites inventoried), Chat, Community,++ Network and Billing.++- **Local powers:** channel and self powers stay with the product and are not staff capabilities.++ They are listed under `local`.++- **Claims** that Network issues: `role`, `is_owner` and `staff_caps`.++- **Rules** that a capability alone does not express:++ - rank and owner protection;++ - no staff power in service, app or mod tokens, and none through delegation;++ - vouching only with the moderate capability;++ - roles change only in Network;++ - every action is reported to the moderation log.++- **`contracts.staff`:** `roles`, `effectiveRole`, `atLeast`, `capabilitiesOf` and `can`. Issued++ `staff_caps` win over the role. A family grant (`staff.moderation.*`) stays inside one area, and++ there is no `staff.*` wildcard.++- **Adoption comes later:** Network issuing the claims, and Live, Chat and Community replacing++ their raw role checks.++++**Capability catalog gaps** (roadmap §30.2), 15 capabilities checked against each service's routes++and production:++++- **Active:** public reads that production serves to anyone:++ - `live.channel.read`, `live.stream.read` and `live.discovery.read`;++ - `community.space.read`, `community.thread.read` and `community.pulse.read`;++ - `search.query.run`: the public index query, suggest and document read. Service tokens keep using++ `search.query.delegate`.++- **Planned:** each names its current routes and the id that guards them today:++ - `live.owner.resolve`: there is no public route yet; `live.lineage.resolve` stays internal;++ - `media.upload.create`, `media.derivative.create`, `media.derivative.read`,++ `media.lifecycle.read` and `media.lifecycle.transition`: Media's guard checks++ `media.object.upload` / `media.object.read` until it accepts these ids;++ - `community.space.manage`;++ - `community.vote.set`: value 0 removes a vote, so there is no `vote.remove`.++- **New `publishing` library manifest** (openvibe-publishing v0.2.1, ADR-019), with no `publishing.*`++ capability. A package has no grant boundary, and the product capabilities (`wiki.*`, `blog.*`,++ `news.*`) already cover those routes. Network's registry-exposure test needs a `publishing` entry++ before Network moves to this release.++++**`mods.mod-manifest@1` 1.0.0 → 1.1.0** (roadmap §28.3 m6). Every 1.0.0 manifest stays valid. The new++fields are all optional:++++- `permissions.readGrants` / `writeGrants` split namespace access into read-only and read-write.++- `billingHooks`: `[{ kind: entitlement|checkout, key, description? }]`, never a price.++- `dependencies`: `[{ id: mod_…, version: <range>, optional? }]`.++++Games still validates a local 1.0.0 copy.++++**Manifests:** the Deals notes now say that Network consumes `deals.watch.matched`.++## 0.33.1 — 2026-09-23Additive: `compat.js` reports no breaking change against v0.33.0.@@@ -2,7 +2,7 @@> Machine-readable contracts for the whole OpenVibe network.--**Status:** alpha, v0.33.1 (first released in Wave 1). 95 schemas (66 of them event payloads), 30 service manifests, 176 capability manifests and 6 namespace manifests. 23 repositories run `openvibe-contracts-check` in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Deployed consumers pin different tags (Games v0.8.0, the rest v0.11.0 to v0.29.0); Network, which serves the registry, pins v0.28.0. No N/N-1 compatibility fixtures exist yet.++**Status:** alpha, v0.34.0 (first released in Wave 1). 147 schemas (117 of them event payloads), 31 service manifests, 191 capability manifests, 6 namespace manifests and the staff capability map. 23 repositories run `openvibe-contracts-check` in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Deployed consumers pin different tags (Games v0.8.0, the rest v0.11.0 to v0.29.0); Network, which serves the registry, pins v0.28.0. No N/N-1 compatibility fixtures exist yet.**Plan:** OpenVibe Development Roadmap, Wave 1 (implementation plan rev 3, §3.1 and §18.11).**License:** AGPL-3.0 (same as every OpenVibe service).@@@ -60,7 +60,7 @@ TypeScript types: `generated/typescript/index.d.ts` (`"types"` in package.json).| `events.event-envelope` | events | durable event shape for OpenVibe.Events (Wave 3) || `media.media-ref` | media | `med_<ULID>` (Wave 4) or transitional `legacy:<app>:<kind>:<id>` |--Manifests: `manifests/services/` (30 services as of v0.30.0: 23 `alpha`, 1 `beta`, 5 `stable` and 1 `placeholder`, realtime; SDK and Shared are released libraries and Examples a repository with CI, none of them a runtime), `manifests/capabilities/` (176 capabilities as of v0.33.0; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Where a service is reachable today (public, loopback only, library) is Network's observed overlay (`server/registry/exposure.js` there); manifest notes say whether a service is deployed and launched publicly.++Manifests: `manifests/services/` (31 services as of v0.34.0: 24 `alpha`, 1 `beta`, 5 `stable` and 1 `retired`, realtime, which ADR-005 closed; SDK, Shared and Publishing are released libraries and Examples a repository with CI, none of them a runtime), `manifests/capabilities/` (191 capabilities as of v0.34.0, 18 of them `planned`; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Where a service is reachable today (public, loopback only, library) is Network's observed overlay (`server/registry/exposure.js` there); manifest notes say whether a service is deployed and launched publicly.**Ids.** Subjects use prefixed ULIDs: `usr_`, `gst_`, `app_`, `mod_`. Services and system actors use slugs (`live`, `media`). Events use `evt_` and Media objects `med_`.@@@ -84,7 +84,7 @@ Three `public` capabilities let a developer app (ADR-014) use OpenVibe.Events wi## Event payload contracts (v0.30)--Every event type has a payload contract, named after the type. The envelope's `version` is the contract's major, and the file is `contracts/events/payloads/<event_type>.v<version>.json`. The owner is the producing service, whose manifest lists the type in `eventsProduced`. Contracts with status `planned` describe events the owner does not emit yet (`ai.run.*`). `tools.job.*` are `active` since v0.33.0, because Tools publishes them.++An event type's payload contract is named after the type. The envelope's `version` is the contract's major, and the file is `contracts/events/payloads/<event_type>.v<version>.json`. The owner is the producing service, whose manifest lists the type in `eventsProduced`. Contracts with status `planned` describe events the owner does not emit yet (`ai.run.*`). `tools.job.*` are `active` since v0.33.0, because Tools publishes them. v0.34.0 added News, Reviews, Coupons, Deals, Trade, Games and `media.object.deleted|visibility_changed` (51, all emitted today). Listed events still without a contract: `chat.message.created`, `chat.dm.created`, `chat.moderation.action`, `codes.app.*` (3), `host.deploy.*` and `host.domain.verified` (4), `media.vod.*`, `media.clip.*`, `media.object.uploaded`, `media.storage.*` (7), `network.app.*`, `network.credential.*`, `network.grant.changed` (5) and `openre.*` (7).```jsif (env.payload.redacted === true) return; // a tombstone (events.tombstone-payload@1)@@@ -124,6 +124,32 @@ One resolver answers "which channel, and whose, is this?" for channel, stream, VThe first input that resolves decides. Every other input that resolves must name the same channel, or the answer is `unresolved` with reason `conflict`. A clip belongs to the clipped channel, never to the clipper. A display name never resolves anything: a request holding only one is answered `unresolved`, `display_name_only`.++## Staff capability map (v0.34)++++Staff roles and what each may do are one contract, `policy.staff-role-map@1`, and one map,++`manifests/policy/staff-roles.json` (ADR-022). OpenVibe.Network owns the roles and issues the++capabilities as claims in its user tokens, and every service checks a `staff.*` capability instead of++comparing role names:++++```js++contracts.staff.can(req.user, 'staff.moderation.bans'); // claims { role, is_owner, staff_caps? }++contracts.staff.capabilitiesOf('global_mod'); // what a role holds++```++++- Roles, lowest first: `user < streamer < global_mod < admin < owner`. A role holds everything below++ it. `user` and `streamer` hold no staff capability.++- `owner` is not a stored role. Tokens carry `role: admin` plus `is_owner: true`, and a role claim of++ `owner` counts as `user`.++- Secrets, money, loyalty grants and making admins are the owner's alone.++- Until Network issues `staff_caps`, `can()` derives them from the role, so the answer is the same++ before and after.++- Channel powers stay with the product and are not staff capabilities: channel owner, channel++ moderators, stream owner and call creator are listed under `local`.++- Each capability lists the role checks it replaces (`gates`, per service, file:line), so adoption in++ Live, Chat and Community is a mechanical swap.++- Staff capabilities never appear in service, app or mod tokens, and are never delegated++ (ADR-003 amendment).++## Tools platform API (v0.33)Every tool on openvibe.tools gets a descriptor, `tools.tool@1` (ADR-027). The page, the run API,@@@ -5,5 +5,5 @@"wave": "W1","dependsOn": ["OpenVibe.Network"],"plan": "OpenVibe Development Roadmap (combined), Wave 1; implementation plan rev 3 section 3.1",-- "updated": "2026-09-23"++ "updated": "2026-09-24"}@@@ -44,5 +44,5 @@"namespacesOwned": ["deals.*"],-- "notes": "Port 4840. Owns deal_products, deal_product_aliases, deal_offers, deal_offer_sources, deal_votes, deal_hotness_snapshots, deal_watches, deal_price_observations, deal_flags (+ deal_stores) in its own SQLite. Consumes OpenVibe.Network (SSO, identity.subject.resolve), OpenVibe.Sources (sources.item.read; sources.item.* events only wake the pull), OpenVibe.Community (community.comment.write, community.comment.moderate) and publishes through OpenVibe.Events (events.event.publish); OpenVibe.Search reads deals.index_document.* from Events; deals.watch.matched is for Network notifications (consumer not built yet)."++ "notes": "Port 4840. Owns deal_products, deal_product_aliases, deal_offers, deal_offer_sources, deal_votes, deal_hotness_snapshots, deal_watches, deal_price_observations, deal_flags (+ deal_stores) in its own SQLite. Consumes OpenVibe.Network (SSO, identity.subject.resolve), OpenVibe.Sources (sources.item.read; sources.item.* events only wake the pull), OpenVibe.Community (community.comment.write, community.comment.moderate) and publishes through OpenVibe.Events (events.event.publish); OpenVibe.Search reads deals.index_document.* from Events; OpenVibe.Network turns deals.watch.matched into notifications (server/notifications/events-consumer.js)."}@@@ -1,12 +1,12 @@{"name": "openvibe-contracts",-- "version": "0.33.1",++ "version": "0.34.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-contracts",-- "version": "0.33.1",++ "version": "0.34.0","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.33.1",++ "version": "0.34.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",
@@ -4,6 +4,151 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -4,6 +4,151 @@ 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.34.0 — 2026-09-24
+
+Additive: `compat.js` reports no breaking change against v0.33.1. Closes the Contracts rows of the
+roadmap completeness audit (§28.3, §30.2, W1 D7, W12, W22).
+
+**51 event payload contracts**, all `active`: every one is emitted by its producer's code today.
+Each was read from the emitter and its call sites. News, Reviews and Deals were also checked against
+envelopes captured from their own code, and Trade against its one production outbox row.
+
+- News (11): `news.source.ingested|failed`, `news.cluster.updated`,
+ `news.story.created|flagged|published|updated|unpublished|retracted`, `news.index_document.*`.
+- Reviews (9): `reviews.entity.merged|split`, `reviews.signal.added|removed`,
+ `reviews.summary.published|updated|unpublished`, `reviews.index_document.*`.
+- Coupons (8): `coupons.coupon.created|updated|expired|disabled`, `coupons.report.created`,
+ `coupons.confidence.changed`, `coupons.index_document.*`.
+- Deals (6): `deals.offer.created|updated|expired`, `deals.vote.changed`, `deals.index_document.*`.
+- Trade (5): `trade.observation.created`, `trade.source.stale|recovered`, `trade.index_document.*`.
+- Games (10): `games.player.joined|left`, `games.skill.leveled`, `games.blueprint.unlocked`,
+ `games.world.saved`, `games.mod.installed|enabled|disabled|grants_changed|revoked`.
+- Media (2): `media.object.deleted` and `media.object.visibility_changed` (OpenVibe.Media 039dc48).
+ Triggers stage them in the transaction that changes the object, and the outbox writes them there.
+ They carry identity and state only: never the title, owner, storage keys or size. A
+ visibility change never repeats the value. The media manifest now lists both.
+
+Every `*.index_document.upserted` is a `search.index-document@1` with the owner fixed. Every
+`.deleted` is a `{ type, id, revision }` tombstone.
+
+Community lists no `eventsProduced`, because it emits nothing yet, so there is no `community.*`
+contract. 29 listed events still have no contract:
+
+- chat: 3
+- codes: 3
+- host: 4
+- media: `vod.*`, `clip.*`, `object.uploaded` and `storage.*`, 7 in all
+- network: 5
+- openre: 7
+
+Found while reading the producers (fixes belong in those repos):
+
+- **News drops some flag events.** `stories.js:238` sends `priority: 'normal'` for `source_updated`
+ flags. The envelope allows only `critical|important|low`, so Events refuses them.
+- **Coupons can publish service notes.** A calling service's note is copied into the public event's
+ `reason`.
+- **Deals under-reports changes.** On the importer paths, `changed` can leave out a `product_id` or
+ `expires_at` change.
+
+**ADR amendments** (dated 2026-09-24):
+
+- **ADR-003, delegated authorization.**
+ - Who may name the acting person, and the rule that a call gets the intersection of the app's
+ grants, the approved scopes and the person's own rights.
+ - Staff power and money are never delegated. Tokens last 5 minutes and cannot be refreshed. Audit
+ names both the app and the person.
+ - The approval UX in force today, and what Network still owes: the capability list on the chooser,
+ stored authorizations, and connected apps with revoke.
+- **ADR-006:**
+ - four placement classes: canonical, hot cache, asset origin, local scratch;
+ - `local` as the first-class dev provider;
+ - the infrequent-access tier deferred at 2,839 objects / 481 GB.
+- **ADR-007:**
+ - PgBouncer transaction pooling;
+ - replicas only for reads that tolerate lag;
+ - SERIALIZABLE plus ordered row locks and 40001 retries on money paths;
+ - Redis never authoritative.
+- **ADR-012:**
+ - The roadmap's ledger names mapped to Billing's transaction types and legs: platform fee,
+ creator earning, payout hold and release, compensating reversals.
+ - OpenCoins stays in Network. This supersedes the Wave 8 line "Network's OpenCoins wallet becomes
+ a Billing client".
+ - Billing's `cashouts`, reversal transactions and VIP plans replace the roadmap's `payouts`,
+ `refunds` and `plans` tables.
+- **ADR-013:**
+ - Package signing and review are named as open owner decisions.
+ - Mod monetization goes only through Billing and VIP primitives.
+ - The manifest 1.1.0 fields (below).
+- **ADR-021, extraction decided.** There is no analytics service. Analytics stays per service through
+ `openvibe-shared/analytics`, and the schema stays in Shared. The decision rests on measured volume:
+ Live has 1.78M raw events in 30 days (58k a day, 502 MB). The ADR names the triggers for a revisit.
+- **ADR-022, the Wave 12 revisit.** The decision stands. The ADR names the trigger for the next
+ revisit.
+
+**Realtime is `retired`** (ADR-005), not a placeholder. A retired manifest offers no domain,
+capability, event, namespace or health path.
+
+**Staff capability map** (W1 D7, ADR-022): a new contract, `policy.staff-role-map@1` (owner `network`), and the map
+in force, `manifests/policy/staff-roles.json`.
+
+- **Roles:** `user < streamer < global_mod < admin < owner`. `owner` is never a stored role: it is
+ `role: admin` plus the `is_owner` claim. `user` and `streamer` hold no staff capability.
+- **32 `staff.<area>.<action>` capabilities**, each with the lowest role that holds it:
+ - 12 for `global_mod`, including `staff.moderation.chat|calls|channels|bans|ip|logs|bypass|pastes|discussions`,
+ `staff.content.view_private` and `staff.content.hide`;
+ - 15 for `admin`, including `staff.users.manage`, `staff.roles.assign`, `staff.site.view|configure`
+ and `staff.streams.end|manage`;
+ - 5 for `owner`: `staff.roles.grant_admin`, `staff.secrets.manage`,
+ `staff.money.freeze|cashouts` and `staff.loyalty.grant`.
+
+ Each lists the file:line role checks it replaces in Live (210 sites inventoried), Chat, Community,
+ Network and Billing.
+- **Local powers:** channel and self powers stay with the product and are not staff capabilities.
+ They are listed under `local`.
+- **Claims** that Network issues: `role`, `is_owner` and `staff_caps`.
+- **Rules** that a capability alone does not express:
+ - rank and owner protection;
+ - no staff power in service, app or mod tokens, and none through delegation;
+ - vouching only with the moderate capability;
+ - roles change only in Network;
+ - every action is reported to the moderation log.
+- **`contracts.staff`:** `roles`, `effectiveRole`, `atLeast`, `capabilitiesOf` and `can`. Issued
+ `staff_caps` win over the role. A family grant (`staff.moderation.*`) stays inside one area, and
+ there is no `staff.*` wildcard.
+- **Adoption comes later:** Network issuing the claims, and Live, Chat and Community replacing
+ their raw role checks.
+
+**Capability catalog gaps** (roadmap §30.2), 15 capabilities checked against each service's routes
+and production:
+
+- **Active:** public reads that production serves to anyone:
+ - `live.channel.read`, `live.stream.read` and `live.discovery.read`;
+ - `community.space.read`, `community.thread.read` and `community.pulse.read`;
+ - `search.query.run`: the public index query, suggest and document read. Service tokens keep using
+ `search.query.delegate`.
+- **Planned:** each names its current routes and the id that guards them today:
+ - `live.owner.resolve`: there is no public route yet; `live.lineage.resolve` stays internal;
+ - `media.upload.create`, `media.derivative.create`, `media.derivative.read`,
+ `media.lifecycle.read` and `media.lifecycle.transition`: Media's guard checks
+ `media.object.upload` / `media.object.read` until it accepts these ids;
+ - `community.space.manage`;
+ - `community.vote.set`: value 0 removes a vote, so there is no `vote.remove`.
+- **New `publishing` library manifest** (openvibe-publishing v0.2.1, ADR-019), with no `publishing.*`
+ capability. A package has no grant boundary, and the product capabilities (`wiki.*`, `blog.*`,
+ `news.*`) already cover those routes. Network's registry-exposure test needs a `publishing` entry
+ before Network moves to this release.
+
+**`mods.mod-manifest@1` 1.0.0 → 1.1.0** (roadmap §28.3 m6). Every 1.0.0 manifest stays valid. The new
+fields are all optional:
+
+- `permissions.readGrants` / `writeGrants` split namespace access into read-only and read-write.
+- `billingHooks`: `[{ kind: entitlement|checkout, key, description? }]`, never a price.
+- `dependencies`: `[{ id: mod_…, version: <range>, optional? }]`.
+
+Games still validates a local 1.0.0 copy.
+
+**Manifests:** the Deals notes now say that Network consumes `deals.watch.matched`.
+
## 0.33.1 — 2026-09-23
## 0.33.1 — 2026-09-23
Additive: `compat.js` reports no breaking change against v0.33.0.
Additive: `compat.js` reports no breaking change against v0.33.0.
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
> Machine-readable contracts for the whole OpenVibe network.
> Machine-readable contracts for the whole OpenVibe network.
-**Status:** alpha, v0.33.1 (first released in Wave 1). 95 schemas (66 of them event payloads), 30 service manifests, 176 capability manifests and 6 namespace manifests. 23 repositories run `openvibe-contracts-check` in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Deployed consumers pin different tags (Games v0.8.0, the rest v0.11.0 to v0.29.0); Network, which serves the registry, pins v0.28.0. No N/N-1 compatibility fixtures exist yet.
+**Status:** alpha, v0.34.0 (first released in Wave 1). 147 schemas (117 of them event payloads), 31 service manifests, 191 capability manifests, 6 namespace manifests and the staff capability map. 23 repositories run `openvibe-contracts-check` in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Deployed consumers pin different tags (Games v0.8.0, the rest v0.11.0 to v0.29.0); Network, which serves the registry, pins v0.28.0. No N/N-1 compatibility fixtures exist yet.
**Plan:** OpenVibe Development Roadmap, Wave 1 (implementation plan rev 3, §3.1 and §18.11).
**Plan:** OpenVibe Development Roadmap, Wave 1 (implementation plan rev 3, §3.1 and §18.11).
**License:** AGPL-3.0 (same as every OpenVibe service).
**License:** AGPL-3.0 (same as every OpenVibe service).
@@ -60,7 +60,7 @@ TypeScript types: `generated/typescript/index.d.ts` (`"types"` in package.json).
@@ -60,7 +60,7 @@ TypeScript types: `generated/typescript/index.d.ts` (`"types"` in package.json).
| `events.event-envelope` | events | durable event shape for OpenVibe.Events (Wave 3) |
| `events.event-envelope` | events | durable event shape for OpenVibe.Events (Wave 3) |
| `media.media-ref` | media | `med_<ULID>` (Wave 4) or transitional `legacy:<app>:<kind>:<id>` |
| `media.media-ref` | media | `med_<ULID>` (Wave 4) or transitional `legacy:<app>:<kind>:<id>` |
-Manifests: `manifests/services/` (30 services as of v0.30.0: 23 `alpha`, 1 `beta`, 5 `stable` and 1 `placeholder`, realtime; SDK and Shared are released libraries and Examples a repository with CI, none of them a runtime), `manifests/capabilities/` (176 capabilities as of v0.33.0; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Where a service is reachable today (public, loopback only, library) is Network's observed overlay (`server/registry/exposure.js` there); manifest notes say whether a service is deployed and launched publicly.
+Manifests: `manifests/services/` (31 services as of v0.34.0: 24 `alpha`, 1 `beta`, 5 `stable` and 1 `retired`, realtime, which ADR-005 closed; SDK, Shared and Publishing are released libraries and Examples a repository with CI, none of them a runtime), `manifests/capabilities/` (191 capabilities as of v0.34.0, 18 of them `planned`; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Where a service is reachable today (public, loopback only, library) is Network's observed overlay (`server/registry/exposure.js` there); manifest notes say whether a service is deployed and launched publicly.
**Ids.** Subjects use prefixed ULIDs: `usr_`, `gst_`, `app_`, `mod_`. Services and system actors use slugs (`live`, `media`). Events use `evt_` and Media objects `med_`.
**Ids.** Subjects use prefixed ULIDs: `usr_`, `gst_`, `app_`, `mod_`. Services and system actors use slugs (`live`, `media`). Events use `evt_` and Media objects `med_`.
@@ -84,7 +84,7 @@ Three `public` capabilities let a developer app (ADR-014) use OpenVibe.Events wi
@@ -84,7 +84,7 @@ Three `public` capabilities let a developer app (ADR-014) use OpenVibe.Events wi
## Event payload contracts (v0.30)
## Event payload contracts (v0.30)
-Every event type has a payload contract, named after the type. The envelope's `version` is the contract's major, and the file is `contracts/events/payloads/<event_type>.v<version>.json`. The owner is the producing service, whose manifest lists the type in `eventsProduced`. Contracts with status `planned` describe events the owner does not emit yet (`ai.run.*`). `tools.job.*` are `active` since v0.33.0, because Tools publishes them.
+An event type's payload contract is named after the type. The envelope's `version` is the contract's major, and the file is `contracts/events/payloads/<event_type>.v<version>.json`. The owner is the producing service, whose manifest lists the type in `eventsProduced`. Contracts with status `planned` describe events the owner does not emit yet (`ai.run.*`). `tools.job.*` are `active` since v0.33.0, because Tools publishes them. v0.34.0 added News, Reviews, Coupons, Deals, Trade, Games and `media.object.deleted|visibility_changed` (51, all emitted today). Listed events still without a contract: `chat.message.created`, `chat.dm.created`, `chat.moderation.action`, `codes.app.*` (3), `host.deploy.*` and `host.domain.verified` (4), `media.vod.*`, `media.clip.*`, `media.object.uploaded`, `media.storage.*` (7), `network.app.*`, `network.credential.*`, `network.grant.changed` (5) and `openre.*` (7).
```js
```js
if (env.payload.redacted === true) return; // a tombstone (events.tombstone-payload@1)
if (env.payload.redacted === true) return; // a tombstone (events.tombstone-payload@1)
@@ -124,6 +124,32 @@ One resolver answers "which channel, and whose, is this?" for channel, stream, V
@@ -124,6 +124,32 @@ One resolver answers "which channel, and whose, is this?" for channel, stream, V
The first input that resolves decides. Every other input that resolves must name the same channel, or the answer is `unresolved` with reason `conflict`. A clip belongs to the clipped channel, never to the clipper. A display name never resolves anything: a request holding only one is answered `unresolved`, `display_name_only`.
The first input that resolves decides. Every other input that resolves must name the same channel, or the answer is `unresolved` with reason `conflict`. A clip belongs to the clipped channel, never to the clipper. A display name never resolves anything: a request holding only one is answered `unresolved`, `display_name_only`.
+## Staff capability map (v0.34)
+
+Staff roles and what each may do are one contract, `policy.staff-role-map@1`, and one map,
+`manifests/policy/staff-roles.json` (ADR-022). OpenVibe.Network owns the roles and issues the
+capabilities as claims in its user tokens, and every service checks a `staff.*` capability instead of
+comparing role names:
+
+```js
+contracts.staff.can(req.user, 'staff.moderation.bans'); // claims { role, is_owner, staff_caps? }
+contracts.staff.capabilitiesOf('global_mod'); // what a role holds
+```
+
+- Roles, lowest first: `user < streamer < global_mod < admin < owner`. A role holds everything below
+ it. `user` and `streamer` hold no staff capability.
+- `owner` is not a stored role. Tokens carry `role: admin` plus `is_owner: true`, and a role claim of
+ `owner` counts as `user`.
+- Secrets, money, loyalty grants and making admins are the owner's alone.
+- Until Network issues `staff_caps`, `can()` derives them from the role, so the answer is the same
+ before and after.
+- Channel powers stay with the product and are not staff capabilities: channel owner, channel
+ moderators, stream owner and call creator are listed under `local`.
+- Each capability lists the role checks it replaces (`gates`, per service, file:line), so adoption in
+ Live, Chat and Community is a mechanical swap.
+- Staff capabilities never appear in service, app or mod tokens, and are never delegated
+ (ADR-003 amendment).
+
## Tools platform API (v0.33)
## Tools platform API (v0.33)
Every tool on openvibe.tools gets a descriptor, `tools.tool@1` (ADR-027). The page, the run API,
Every tool on openvibe.tools gets a descriptor, `tools.tool@1` (ADR-027). The page, the run API,
@@ -5,5 +5,5 @@
@@ -5,5 +5,5 @@
"wave": "W1",
"wave": "W1",
"dependsOn": ["OpenVibe.Network"],
"dependsOn": ["OpenVibe.Network"],
"plan": "OpenVibe Development Roadmap (combined), Wave 1; implementation plan rev 3 section 3.1",
"plan": "OpenVibe Development Roadmap (combined), Wave 1; implementation plan rev 3 section 3.1",
- "updated": "2026-09-23"
+ "updated": "2026-09-24"
}
}
@@ -44,5 +44,5 @@
@@ -44,5 +44,5 @@
"namespacesOwned": [
"namespacesOwned": [
"deals.*"
"deals.*"
],
],
- "notes": "Port 4840. Owns deal_products, deal_product_aliases, deal_offers, deal_offer_sources, deal_votes, deal_hotness_snapshots, deal_watches, deal_price_observations, deal_flags (+ deal_stores) in its own SQLite. Consumes OpenVibe.Network (SSO, identity.subject.resolve), OpenVibe.Sources (sources.item.read; sources.item.* events only wake the pull), OpenVibe.Community (community.comment.write, community.comment.moderate) and publishes through OpenVibe.Events (events.event.publish); OpenVibe.Search reads deals.index_document.* from Events; deals.watch.matched is for Network notifications (consumer not built yet)."
+ "notes": "Port 4840. Owns deal_products, deal_product_aliases, deal_offers, deal_offer_sources, deal_votes, deal_hotness_snapshots, deal_watches, deal_price_observations, deal_flags (+ deal_stores) in its own SQLite. Consumes OpenVibe.Network (SSO, identity.subject.resolve), OpenVibe.Sources (sources.item.read; sources.item.* events only wake the pull), OpenVibe.Community (community.comment.write, community.comment.moderate) and publishes through OpenVibe.Events (events.event.publish); OpenVibe.Search reads deals.index_document.* from Events; OpenVibe.Network turns deals.watch.matched into notifications (server/notifications/events-consumer.js)."
}
}
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.33.1",
+ "version": "0.34.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.33.1",
+ "version": "0.34.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.33.1",
+ "version": "0.34.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",