openvibers/openvibe.contracts Commits

v0.14.0: mods.mod-manifest@1 (ADR-013); OpenVibe.Games capabilities games.mod.manage|read, games.world.announce, games.prop.place; Network plans mods.grant.manage

OP OpenVibers committed 23 Sept 2026 · e31bb78357 · 14 files, +532 −13


Changes

ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex e76f2c0e..aa2cf6ee 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -147,6 +147,16 @@
"compatibility": "backward",
"status": "active",
"adr": "ADR-017"
++ },
++ {
++ "id": "mods.mod-manifest",
++ "version": "1.0.0",
++ "owner": "contracts",
++ "schema": "mods/mod-manifest.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-013"
}
]
}
ddiff --git a/contracts/mods/mod-manifest.v1.json b/contracts/mods/mod-manifest.v1.json
nnew file mode 100644
iindex 00000000..2f5ae9bd
---- /dev/null
++++ b/contracts/mods/mod-manifest.v1.json
@@@ -0,0 +1,134 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/mods/mod-manifest.v1.json",
++ "title": "ModManifest",
++ "description": "A mod as the platform knows it (ADR-013): who publishes it, which runtime runs it, which capabilities it asks for and the resources it may use. Requested capabilities are only a request: the install's approved subset is the grant, and trust tiers are install metadata that never change a grant check. The runtime-specific payload (a data pack, a script bundle) is not part of the manifest.",
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "version",
++ "publisher",
++ "target",
++ "runtime",
++ "permissions",
++ "resources",
++ "compatibility"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Stable mod id; its principal subject is mod:<id>."
++ },
++ "name": { "type": "string", "minLength": 1, "maxLength": 80 },
++ "version": {
++ "type": "string",
++ "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$",
++ "description": "Semantic version of this release."
++ },
++ "description": { "type": "string", "maxLength": 1000 },
++ "publisher": {
++ "$ref": "../identity/subject-ref.v1.json",
++ "description": "Who publishes the mod (a user or an app subject)."
++ },
++ "target": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}\\.[a-z][a-z0-9-]{1,39}$",
++ "description": "Where the mod runs: <service>.<surface>, e.g. games.browser, games.source, live.overlay."
++ },
++ "runtime": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}@[1-9][0-9]{0,3}$",
++ "description": "Runtime adapter and its major version, e.g. games-content@1 (declarative data pack) or source-quickjs@1."
++ },
++ "permissions": {
++ "type": "object",
++ "required": ["capabilities"],
++ "additionalProperties": false,
++ "properties": {
++ "capabilities": {
++ "description": "Capability ids the mod asks for (3+ segments). The target runtime binds only those it implements, and only once granted.",
++ "type": "array",
++ "maxItems": 64,
++ "uniqueItems": true,
++ "items": { "type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$" }
++ },
++ "events": {
++ "description": "Event types the mod wants delivered to it.",
++ "type": "array",
++ "maxItems": 64,
++ "uniqueItems": true,
++ "items": { "type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$" }
++ },
++ "modules": {
++ "description": "User-module namespaces the mod wants to read or write (a trailing .* names a family).",
++ "type": "array",
++ "maxItems": 32,
++ "uniqueItems": true,
++ "items": { "type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*(\\.\\*)?$" }
++ },
++ "mediaNamespaces": {
++ "description": "Media namespaces the mod wants to read or write.",
++ "type": "array",
++ "maxItems": 32,
++ "uniqueItems": true,
++ "items": { "type": "string", "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z0-9_-]+)*$" }
++ }
++ }
++ },
++ "resources": {
++ "type": "object",
++ "required": ["cpuMs", "memoryMb", "storageMb"],
++ "additionalProperties": false,
++ "description": "The budget the mod asks for. Runtimes meter it; the sandbox that enforces it lives in OpenVibe.Host (Stage C).",
++ "properties": {
++ "cpuMs": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1000,
++ "description": "CPU milliseconds per tick (game runtimes) or per request."
++ },
++ "memoryMb": { "type": "integer", "minimum": 0, "maximum": 4096 },
++ "storageMb": { "type": "integer", "minimum": 0, "maximum": 102400 },
++ "outboundHosts": {
++ "description": "Hosts the mod may reach over the network. Empty or absent = none.",
++ "type": "array",
++ "maxItems": 32,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$"
++ }
++ }
++ }
++ },
++ "assets": {
++ "description": "Assets the mod ships, as Media object references.",
++ "type": "array",
++ "maxItems": 256,
++ "items": { "$ref": "../media/media-ref.v1.json" }
++ },
++ "compatibility": {
++ "type": "object",
++ "required": ["runtime"],
++ "additionalProperties": false,
++ "properties": {
++ "runtime": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 100,
++ "description": "Semver range of the runtime this release works with, e.g. \">=1.0.0 <2.0.0\"."
++ },
++ "contracts": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 100,
++ "description": "Semver range of openvibe-contracts releases it was built against."
++ }
++ }
++ },
++ "homepage": { "type": "string", "pattern": "^https://[^\\s]{1,2000}$" }
++ }
++}
ddiff --git a/fixtures/mods.mod-manifest/invalid/no-permissions.json b/fixtures/mods.mod-manifest/invalid/no-permissions.json
nnew file mode 100644
iindex 00000000..3c83ce9e
---- /dev/null
++++ b/fixtures/mods.mod-manifest/invalid/no-permissions.json
@@@ -0,0 +1 @@
++{ "id": "mod_01JABCDEFGHJKMNPQRSTVWXYZ0", "name": "X", "version": "1.0.0", "publisher": { "type": "user", "id": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0" }, "target": "games.browser", "runtime": "games-content@1", "resources": { "cpuMs": 1, "memoryMb": 0, "storageMb": 0 }, "compatibility": { "runtime": ">=1.0.0" } }
ddiff --git a/fixtures/mods.mod-manifest/valid/town-square.json b/fixtures/mods.mod-manifest/valid/town-square.json
nnew file mode 100644
iindex 00000000..dcd5ccfb
---- /dev/null
++++ b/fixtures/mods.mod-manifest/valid/town-square.json
@@@ -0,0 +1,12 @@
++{
++ "id": "mod_01JABCDEFGHJKMNPQRSTVWXYZ0",
++ "name": "Town Square",
++ "version": "1.0.0",
++ "description": "A public workbench and a greeting.",
++ "publisher": { "type": "user", "id": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0" },
++ "target": "games.browser",
++ "runtime": "games-content@1",
++ "permissions": { "capabilities": ["games.world.announce", "games.prop.place"] },
++ "resources": { "cpuMs": 1, "memoryMb": 0, "storageMb": 0 },
++ "compatibility": { "runtime": ">=1.0.0 <2.0.0" }
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex c9e6d5a3..8a417aa2 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -1620,6 +1620,185 @@
}
}
}
++ },
++ "mods.mod-manifest": {
++ "version": "1.0.0",
++ "owner": "contracts",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/mods/mod-manifest.v1.json",
++ "title": "ModManifest",
++ "description": "A mod as the platform knows it (ADR-013): who publishes it, which runtime runs it, which capabilities it asks for and the resources it may use. Requested capabilities are only a request: the install's approved subset is the grant, and trust tiers are install metadata that never change a grant check. The runtime-specific payload (a data pack, a script bundle) is not part of the manifest.",
++ "type": "object",
++ "required": [
++ "id",
++ "name",
++ "version",
++ "publisher",
++ "target",
++ "runtime",
++ "permissions",
++ "resources",
++ "compatibility"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Stable mod id; its principal subject is mod:<id>."
++ },
++ "name": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80
++ },
++ "version": {
++ "type": "string",
++ "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$",
++ "description": "Semantic version of this release."
++ },
++ "description": {
++ "type": "string",
++ "maxLength": 1000
++ },
++ "publisher": {
++ "$ref": "../identity/subject-ref.v1.json",
++ "description": "Who publishes the mod (a user or an app subject)."
++ },
++ "target": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}\\.[a-z][a-z0-9-]{1,39}$",
++ "description": "Where the mod runs: <service>.<surface>, e.g. games.browser, games.source, live.overlay."
++ },
++ "runtime": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}@[1-9][0-9]{0,3}$",
++ "description": "Runtime adapter and its major version, e.g. games-content@1 (declarative data pack) or source-quickjs@1."
++ },
++ "permissions": {
++ "type": "object",
++ "required": [
++ "capabilities"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "capabilities": {
++ "description": "Capability ids the mod asks for (3+ segments). The target runtime binds only those it implements, and only once granted.",
++ "type": "array",
++ "maxItems": 64,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$"
++ }
++ },
++ "events": {
++ "description": "Event types the mod wants delivered to it.",
++ "type": "array",
++ "maxItems": 64,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$"
++ }
++ },
++ "modules": {
++ "description": "User-module namespaces the mod wants to read or write (a trailing .* names a family).",
++ "type": "array",
++ "maxItems": 32,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*(\\.\\*)?$"
++ }
++ },
++ "mediaNamespaces": {
++ "description": "Media namespaces the mod wants to read or write.",
++ "type": "array",
++ "maxItems": 32,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z0-9_-]+)*$"
++ }
++ }
++ }
++ },
++ "resources": {
++ "type": "object",
++ "required": [
++ "cpuMs",
++ "memoryMb",
++ "storageMb"
++ ],
++ "additionalProperties": false,
++ "description": "The budget the mod asks for. Runtimes meter it; the sandbox that enforces it lives in OpenVibe.Host (Stage C).",
++ "properties": {
++ "cpuMs": {
++ "type": "number",
++ "minimum": 0,
++ "maximum": 1000,
++ "description": "CPU milliseconds per tick (game runtimes) or per request."
++ },
++ "memoryMb": {
++ "type": "integer",
++ "minimum": 0,
++ "maximum": 4096
++ },
++ "storageMb": {
++ "type": "integer",
++ "minimum": 0,
++ "maximum": 102400
++ },
++ "outboundHosts": {
++ "description": "Hosts the mod may reach over the network. Empty or absent = none.",
++ "type": "array",
++ "maxItems": 32,
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "pattern": "^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,63}$"
++ }
++ }
++ }
++ },
++ "assets": {
++ "description": "Assets the mod ships, as Media object references.",
++ "type": "array",
++ "maxItems": 256,
++ "items": {
++ "$ref": "../media/media-ref.v1.json"
++ }
++ },
++ "compatibility": {
++ "type": "object",
++ "required": [
++ "runtime"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "runtime": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 100,
++ "description": "Semver range of the runtime this release works with, e.g. \">=1.0.0 <2.0.0\"."
++ },
++ "contracts": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 100,
++ "description": "Semver range of openvibe-contracts releases it was built against."
++ }
++ }
++ },
++ "homepage": {
++ "type": "string",
++ "pattern": "^https://[^\\s]{1,2000}$"
++ }
++ }
++ }
}
}
}
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex b6e9649a..ff476899 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -3198,3 +3198,89 @@ export interface SourceItem {
change_seq?: number;
revisions?: unknown[];
}
++
++/** [email protected] (owner: contracts) */
++/**
++ * A mod as the platform knows it (ADR-013): who publishes it, which runtime runs it, which capabilities it asks for and the resources it may use. Requested capabilities are only a request: the install's approved subset is the grant, and trust tiers are install metadata that never change a grant check. The runtime-specific payload (a data pack, a script bundle) is not part of the manifest.
++ */
++export interface ModManifest {
++ /**
++ * Stable mod id; its principal subject is mod:<id>.
++ */
++ id: string;
++ name: string;
++ /**
++ * Semantic version of this release.
++ */
++ version: string;
++ description?: string;
++ publisher: SubjectRef;
++ /**
++ * Where the mod runs: <service>.<surface>, e.g. games.browser, games.source, live.overlay.
++ */
++ target: string;
++ /**
++ * Runtime adapter and its major version, e.g. games-content@1 (declarative data pack) or source-quickjs@1.
++ */
++ runtime: string;
++ permissions: {
++ /**
++ * Capability ids the mod asks for (3+ segments). The target runtime binds only those it implements, and only once granted.
++ *
++ * @maxItems 64
++ */
++ capabilities: string[];
++ /**
++ * Event types the mod wants delivered to it.
++ *
++ * @maxItems 64
++ */
++ events?: string[];
++ /**
++ * User-module namespaces the mod wants to read or write (a trailing .* names a family).
++ *
++ * @maxItems 32
++ */
++ modules?: string[];
++ /**
++ * Media namespaces the mod wants to read or write.
++ *
++ * @maxItems 32
++ */
++ mediaNamespaces?: string[];
++ };
++ /**
++ * The budget the mod asks for. Runtimes meter it; the sandbox that enforces it lives in OpenVibe.Host (Stage C).
++ */
++ resources: {
++ /**
++ * CPU milliseconds per tick (game runtimes) or per request.
++ */
++ cpuMs: number;
++ memoryMb: number;
++ storageMb: number;
++ /**
++ * Hosts the mod may reach over the network. Empty or absent = none.
++ *
++ * @maxItems 32
++ */
++ outboundHosts?: string[];
++ };
++ /**
++ * Assets the mod ships, as Media object references.
++ *
++ * @maxItems 256
++ */
++ assets?: MediaRef[];
++ compatibility: {
++ /**
++ * Semver range of the runtime this release works with, e.g. ">=1.0.0 <2.0.0".
++ */
++ runtime: string;
++ /**
++ * Semver range of openvibe-contracts releases it was built against.
++ */
++ contracts?: string;
++ };
++ homepage?: string;
++}
ddiff --git a/manifests/capabilities/games.mod.manage.json b/manifests/capabilities/games.mod.manage.json
nnew file mode 100644
iindex 00000000..00e31939
---- /dev/null
++++ b/manifests/capabilities/games.mod.manage.json
@@@ -0,0 +1,28 @@
++{
++ "id": "games.mod.manage",
++ "version": "1.0",
++ "owner": "games",
++ "status": "active",
++ "visibility": "first-party",
++ "description": "Manage mod installs in the browser game's registry (ADR-013): install a mods/mod-manifest.v1 manifest with its games-content@1 pack, approve or revoke individual capabilities of an install, enable, disable, and revoke an install for good (its effects end on the next server tick). Every action is audited and published as a games.mod.* event. Trust tiers are metadata and never grant anything. Also accepted from an openvibe.network owner/admin session.",
++ "inputSchema": "mods.mod-manifest@1",
++ "permissions": ["games:mods:admin"],
++ "resourceConstraints": ["none"],
++ "quotaClass": "games-admin",
++ "events": [
++ "games.mod.installed",
++ "games.mod.enabled",
++ "games.mod.disabled",
++ "games.mod.grants_changed",
++ "games.mod.revoked"
++ ],
++ "implementedBy": [
++ "POST /api/v1/mods",
++ "POST /api/v1/mods/:id/enable",
++ "POST /api/v1/mods/:id/disable",
++ "POST /api/v1/mods/:id/revoke",
++ "POST /api/v1/mods/:id/grants",
++ "DELETE /api/v1/mods/:id/grants/:capability",
++ "GET /api/v1/mods/:id/audit"
++ ]
++}
ddiff --git a/manifests/capabilities/games.mod.read.json b/manifests/capabilities/games.mod.read.json
nnew file mode 100644
iindex 00000000..e1e63cfc
---- /dev/null
++++ b/manifests/capabilities/games.mod.read.json
@@@ -0,0 +1,14 @@
++{
++ "id": "games.mod.read",
++ "version": "1.0",
++ "owner": "games",
++ "status": "active",
++ "visibility": "public",
++ "description": "Read the browser game's installed mods: manifest, content pack, status, trust tier, requested and granted capabilities. Public; no token needed.",
++ "outputSchema": "mods.mod-manifest@1",
++ "permissions": ["games:mods:read"],
++ "resourceConstraints": ["none"],
++ "quotaClass": "games-read",
++ "events": [],
++ "implementedBy": ["GET /api/v1/mods", "GET /api/v1/mods/:id"]
++}
ddiff --git a/manifests/capabilities/games.prop.place.json b/manifests/capabilities/games.prop.place.json
nnew file mode 100644
iindex 00000000..aa0d8d94
---- /dev/null
++++ b/manifests/capabilities/games.prop.place.json
@@@ -0,0 +1,15 @@
++{
++ "id": "games.prop.place",
++ "version": "1.0",
++ "owner": "games",
++ "status": "active",
++ "visibility": "public",
++ "description": "Runtime binding for mods (games-content@1): place and remove inert props (existing content items without health, storage, shop, machine or vehicle-part capabilities) in the world, owned by the mod so players cannot pick them up or move them. The props exist only while the capability is granted and the install enabled: revoking either despawns them on the next tick.",
++ "permissions": ["games:world:place"],
++ "resourceConstraints": ["none"],
++ "quotaClass": "games-mod-runtime",
++ "events": [],
++ "implementedBy": [
++ "mod runtime binding ModApi.placeProp / ModApi.removeProp (apps/server/src/mods/runtime.ts)"
++ ]
++}
ddiff --git a/manifests/capabilities/games.world.announce.json b/manifests/capabilities/games.world.announce.json
nnew file mode 100644
iindex 00000000..d3976cc8
---- /dev/null
++++ b/manifests/capabilities/games.world.announce.json
@@@ -0,0 +1,13 @@
++{
++ "id": "games.world.announce",
++ "version": "1.0",
++ "owner": "games",
++ "status": "active",
++ "visibility": "public",
++ "description": "Runtime binding for mods (games-content@1): broadcast a short server announcement to every player in the world, prefixed with the mod's name, at most once a minute per announcement. Checked at call time against the install's approved capabilities; a revoked grant stops it on the next tick.",
++ "permissions": ["games:world:announce"],
++ "resourceConstraints": ["none"],
++ "quotaClass": "games-mod-runtime",
++ "events": [],
++ "implementedBy": ["mod runtime binding ModApi.announce (apps/server/src/mods/runtime.ts)"]
++}
ddiff --git a/manifests/capabilities/mods.grant.manage.json b/manifests/capabilities/mods.grant.manage.json
nnew file mode 100644
iindex 00000000..9ddcb238
---- /dev/null
++++ b/manifests/capabilities/mods.grant.manage.json
@@@ -0,0 +1,14 @@
++{
++ "id": "mods.grant.manage",
++ "version": "1.0",
++ "owner": "network",
++ "status": "planned",
++ "visibility": "first-party",
++ "description": "Planned (ADR-013, grants in Network): create the `mod` principal (mod:mod_<ULID>) for an install and approve or revoke the subset of its manifest's requested capabilities, so a mod's grants are checked like any other principal's. Until Network implements it, each runtime keeps the approved subset itself (OpenVibe.Games: mod_grants).",
++ "inputSchema": "mods.mod-manifest@1",
++ "permissions": ["network:grants:admin"],
++ "resourceConstraints": ["none"],
++ "quotaClass": "network-admin",
++ "events": [],
++ "implementedBy": []
++}
ddiff --git a/manifests/services/games.json b/manifests/services/games.json
iindex 849f440a..6f6a5c77 100644
---- a/manifests/services/games.json
++++ b/manifests/services/games.json
@@@ -1,22 +1,34 @@
{
-- "version": "0.1.0",
++ "version": "0.2.0",
"contractRanges": {
-- "openvibe-contracts": ">=0.2.0 <1.0.0"
++ "openvibe-contracts": ">=0.8.0 <1.0.0"
},
"id": "games",
"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",
-- "capabilities": [],
-- "eventsProduced": [],
++ "capabilities": [
++ "games.mod.manage",
++ "games.mod.read",
++ "games.world.announce",
++ "games.prop.place"
++ ],
++ "eventsProduced": [
++ "games.player.joined",
++ "games.player.left",
++ "games.skill.leveled",
++ "games.blueprint.unlocked",
++ "games.world.saved",
++ "games.mod.installed",
++ "games.mod.enabled",
++ "games.mod.disabled",
++ "games.mod.grants_changed",
++ "games.mod.revoked"
++ ],
"eventsConsumed": [],
-- "namespacesOwned": [
-- "games.*"
-- ]
++ "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."
}
ddiff --git a/manifests/services/network.json b/manifests/services/network.json
iindex ebe5ffb7..510cb38d 100644
---- a/manifests/services/network.json
++++ b/manifests/services/network.json
@@@ -24,7 +24,8 @@
"network.coins.transfer",
"network.notifications.push",
"network.modules.read",
-- "network.modules.write"
++ "network.modules.write",
++ "mods.grant.manage"
],
"eventsProduced": [],
"eventsConsumed": [],
ddiff --git a/package.json b/package.json
iindex b82b707d..8502b1fe 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.13.0",
++ "version": "0.14.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",