openvibers/openvibe.contracts Commits
The machines hub: the machine, its roles and its events (plan L34) (#269)
Changes
@@@ -1,5 +1,34 @@# Changelog++## 0.261.0 — 2026-10-31++++- **The machines hub** (plan L34, steps M2 and M4). A person's computers are the network's: Bot stays the device++ registry underneath, and Network fronts it on openvibe.network/machines for the person and for apps the person++ consented to.++ - **`network.machine@1`:** one machine as the hub shows it — its id (Bot's `dev_…`), name, kind, os, whether it is++ online, when it was last seen, its `roles` and what it does right now (`running`: its Codes jobs, its desks, its++ local models, its robots).++ - **`network.machine-roles@1` and `network.machine-role@1`:** the catalogue of roles. Each role has a name, what it++ installs on the machine and what the hub shows. A product declares its roles in its own service manifest++ (`machineRoles`), so the catalogue is the union of what the products declare: `codes.worker` (Codes), `desks.host`++ (Run), `ai.host` (AI) and `bot.robot` (Bot) are the first four.++ - **`network.machine-roles-put@1`:** the roles a machine has after a change — the whole set, not a diff.++ - **`network.machine-pairing@1`:** a one-time pairing code, its expiry and the command to run on the machine.++ - **`network.machine-list-result@1` and `network.machine-manage-result@1`:** what the hub answers with.++ - **`bot.machine-manage-request@1` and `bot.machine-manage-result@1`:** what Bot's machines API takes and answers++ with. `bot.device@1` gains `roles` and `os` (both optional; the old shape still holds).++ - **The capabilities:** `network.machines.read` and `network.machines.manage` (owner `network`, public: an app may++ hold them with the person's consent, and may then do only what the person could) and `bot.machine.manage` (owner++ `bot`, first-party: Network holds it for the hub, so Bot stays the authority and checks the machine is the named++ person's own).++ - **The events:** `network.machine.online`, `network.machine.offline`, `network.machine.paired`,++ `network.machine.roles_changed` and `network.machine.revoked` — Network publishes them through OpenVibe.Events.++ - **Presence policy:** `network.machine.online` and `network.machine.offline` are a machine's connectivity (the++ machines hub, plan L34), not a person's presence, and are allowed like `bot.robot.online` and `bot.robot.offline`++ (ADR-043). Any other `.online`, `.offline` or presence event type is still refused.++ - **`registry.service-manifest@1` gains `machineRoles`** (optional): the roles a product declares. The manifest++ keeps its old shape; a manifest without the field means a product with no machine role.++## 0.260.0 — 2026-10-31- **One AI meter across the network** (plan L40): every AI call on the network leaves one usage record, whoever made it.@@@ -68,6 +68,21 @@},"online": {"type": "boolean"++ },++ "roles": {++ "type": "array",++ "items": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"++ },++ "description": "The machine roles the owner set (plan L34: the catalogue is network.machine-roles@1). Absent on a device that has none."++ },++ "os": {++ "type": [++ "string",++ "null"++ ],++ "description": "The operating system the device reported."}}}nnew file mode 100644@@@ -0,0 +1,27 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/bot/machine-manage-request.v1.json",++ "title": "BotMachineManageRequest",++ "description": "bot.machine-manage-request@1: what a caller changes on a machine (plan L34, step M2). A rename gives a name; a roles change gives the whole set of roles (network.machine-roles-put@1's shape). The same body is what the network's hub sends to Bot, so both sides speak one shape.",++ "type": "object",++ "additionalProperties": false,++ "anyOf": [++ { "required": ["name"] },++ { "required": ["roles"] }++ ],++ "properties": {++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80,++ "description": "The name the owner gives the machine. Absent when the call changes something else."++ },++ "roles": {++ "type": "array",++ "uniqueItems": true,++ "maxItems": 8,++ "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$" },++ "description": "The machine's roles after the change. Absent when the call changes something else."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,12 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/bot/machine-manage-result.v1.json",++ "title": "BotMachineManageResult",++ "description": "bot.machine-manage-result@1: the machine as it is after the change (plan L34, step M2). Bot is the registry underneath: it answers with its own device record, never with a credential.",++ "type": "object",++ "required": ["machine"],++ "additionalProperties": false,++ "properties": {++ "machine": { "$ref": "device.v1.json" }++ }++}@@@ -5887,6 +5887,132 @@"status": "active","adr": "ADR-034"},++ {++ "id": "network.machine",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/machine.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.machine-list-result",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/machine-list-result.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.machine-manage-result",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/machine-manage-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "bot.machine-manage-request",++ "version": "1.0.0",++ "owner": "bot",++ "schema": "bot/machine-manage-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "bot.machine-manage-result",++ "version": "1.0.0",++ "owner": "bot",++ "schema": "bot/machine-manage-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.machine-role",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/machine-role.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.machine-roles",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/machine-roles.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.machine-roles-put",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/machine-roles-put.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.machine-pairing",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/machine-pairing.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.machine.online",++ "version": "1.0.0",++ "owner": "network",++ "schema": "events/payloads/network.machine.online.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.machine.offline",++ "version": "1.0.0",++ "owner": "network",++ "schema": "events/payloads/network.machine.offline.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.machine.paired",++ "version": "1.0.0",++ "owner": "network",++ "schema": "events/payloads/network.machine.paired.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.machine.roles_changed",++ "version": "1.0.0",++ "owner": "network",++ "schema": "events/payloads/network.machine.roles_changed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "network.machine.revoked",++ "version": "1.0.0",++ "owner": "network",++ "schema": "events/payloads/network.machine.revoked.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },{"id": "network.avatar-write-request","version": "1.0.0",nnew file mode 100644@@@ -0,0 +1,15 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.offline.v1.json",++ "title": "NetworkMachineOfflinePayload",++ "description": "network.machine.offline v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person went offline: it is no longer connected to Bot. Network republishes Bot's change, so a consumer of the network's events hears about a machine once, in one place. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",++ "type": "object",++ "required": ["machine_id", "owner", "at"],++ "additionalProperties": false,++ "properties": {++ "machine_id": { "type": "string", "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$" },++ "principal_id": { "type": "string", "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The machine's node principal on Network, when it has one." },++ "owner": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The person the machine belongs to." },++ "at": { "type": "string", "format": "date-time" }++ }++}nnew file mode 100644@@@ -0,0 +1,15 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.online.v1.json",++ "title": "NetworkMachineOnlinePayload",++ "description": "network.machine.online v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person (a Bot device of kind machine) came online: it checked in and is connected to Bot. Network republishes Bot's change (Bot reports it to Network, which writes this event), so a consumer of the network's events hears about a machine once, in one place. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",++ "type": "object",++ "required": ["machine_id", "owner", "at"],++ "additionalProperties": false,++ "properties": {++ "machine_id": { "type": "string", "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$" },++ "principal_id": { "type": "string", "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The machine's node principal on Network, when it has one." },++ "owner": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The person the machine belongs to." },++ "at": { "type": "string", "format": "date-time" }++ }++}nnew file mode 100644@@@ -0,0 +1,16 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.paired.v1.json",++ "title": "NetworkMachinePairedPayload",++ "description": "network.machine.paired v1 (OpenVibe.Network, plan L34: the machines hub). A machine was paired with a person's account: it redeemed the one-time code and became theirs. It holds a credential from then on; the code is spent. The machine's device id in Bot is absent until the machine checks in for the first time. Envelope: subject { type: machine, id: <nod_…> }, visibility internal, actor service:network.",++ "type": "object",++ "required": ["principal_id", "owner", "at"],++ "additionalProperties": false,++ "properties": {++ "machine_id": { "type": "string", "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The machine's device id in Bot's registry, when Bot already knows the device." },++ "node_id": { "type": "string", "pattern": "^n-[a-z0-9]{26}$", "description": "The machine's node id (platform_node_principals.node_id)." },++ "principal_id": { "type": "string", "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The node principal the pairing created. Also the envelope subject id." },++ "owner": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The person the machine belongs to." },++ "at": { "type": "string", "format": "date-time" }++ }++}nnew file mode 100644@@@ -0,0 +1,16 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.revoked.v1.json",++ "title": "NetworkMachineRevokedPayload",++ "description": "network.machine.revoked v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person was revoked: it is no longer theirs and its credential is dead. The machine's credential and its hash are never carried. A second revoke changes nothing and emits nothing. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",++ "type": "object",++ "required": ["machine_id", "owner", "at"],++ "additionalProperties": false,++ "properties": {++ "machine_id": { "type": "string", "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$" },++ "principal_id": { "type": "string", "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The machine's node principal on Network, when it had one." },++ "owner": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The person the machine belonged to." },++ "reason": { "type": "string", "minLength": 1, "maxLength": 500, "description": "Why it was revoked, when the caller gave one. Absent otherwise." },++ "at": { "type": "string", "format": "date-time" }++ }++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.roles_changed.v1.json",++ "title": "NetworkMachineRolesChangedPayload",++ "description": "network.machine.roles_changed v1 (OpenVibe.Network, plan L34, step M4). A machine's roles changed: the whole set after the change, from the catalogue (network.machine-roles@1). The machine applies them at its next check-in, so this event says what was asked for, not what the machine has already done. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",++ "type": "object",++ "required": ["machine_id", "owner", "roles", "at"],++ "additionalProperties": false,++ "properties": {++ "machine_id": { "type": "string", "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$" },++ "owner": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The person the machine belongs to." },++ "roles": {++ "type": "array",++ "uniqueItems": true,++ "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$" },++ "description": "The machine's roles after the change."++ },++ "changed_by": {++ "type": "string",++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|app:[a-z0-9-]+)$",++ "description": "Who asked for the change: the person, or an app acting with their consent."++ },++ "at": { "type": "string", "format": "date-time" }++ }++}nnew file mode 100644@@@ -0,0 +1,15 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine-list-result.v1.json",++ "title": "NetworkMachineListResult",++ "description": "network.machine-list-result@1: a person's machines, as the network's hub lists them (plan L34, step M2: GET /api/v1/machines). Only the caller's own machines are ever in it.",++ "type": "object",++ "required": ["machines"],++ "additionalProperties": false,++ "properties": {++ "machines": {++ "type": "array",++ "items": { "$ref": "machine.v1.json" }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,12 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine-manage-result.v1.json",++ "title": "NetworkMachineManageResult",++ "description": "network.machine-manage-result@1: the machine as it is after a change (plan L34, step M2: PATCH /api/v1/machines/:id, PUT /api/v1/machines/:id/roles, POST /api/v1/machines/:id/revoke). The pairing route answers network.machine-pairing@1 instead, because a pairing is a code and not a machine.",++ "type": "object",++ "required": ["machine"],++ "additionalProperties": false,++ "properties": {++ "machine": { "$ref": "machine.v1.json" }++ }++}nnew file mode 100644@@@ -0,0 +1,27 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine-pairing.v1.json",++ "title": "NetworkMachinePairing",++ "description": "network.machine-pairing@1: a one-time code that pairs a machine with the person's account (POST /api/v1/machines/pairings, capability machines.manage). The code is shown once, expires, and works once: the machine redeems it and becomes the person's. The command is what the owner runs on the machine (openvibe.bot/install). The code is never stored in the clear and never returned again.",++ "type": "object",++ "required": ["code", "expires_at", "installer"],++ "additionalProperties": false,++ "properties": {++ "code": {++ "type": "string",++ "pattern": "^[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}$",++ "description": "The one-time pairing code, eight Crockford base32 characters in two groups (XXXX-XXXX)."++ },++ "pairing_id": {++ "type": "string",++ "pattern": "^pair_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The pairing's id, as the install command carries it. Absent when the authority behind it does not name one."++ },++ "expires_at": { "type": "string", "format": "date-time", "description": "When the code stops working." },++ "installer": {++ "type": "string",++ "maxLength": 500,++ "description": "The command the owner runs on the machine (openvibe.bot/install, with the network, the pairing id and the code)."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine-role.v1.json",++ "title": "NetworkMachineRole",++ "description": "network.machine-role@1: one role a machine can have (plan L34, step M4). A role is what the machine does for the network: a name for the owner, what it installs on the machine and what the hub shows. The catalogue is network.machine-roles@1; a role outside it is refused.",++ "type": "object",++ "required": ["id", "name", "installs", "shows"],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$",++ "description": "The role's id: the product that owns it, then what it is (codes.worker, desks.host, ai.host, bot.robot)."++ },++ "name": { "type": "string", "maxLength": 60, "description": "The role's name, in the owner's words." },++ "installs": { "type": "string", "maxLength": 200, "description": "What the role puts on the machine (the job runner, the desk sandbox, the local model server)." },++ "shows": { "type": "string", "maxLength": 200, "description": "What the hub shows for a machine with this role." }++ }++}nnew file mode 100644@@@ -0,0 +1,18 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine-roles-put.v1.json",++ "title": "NetworkMachineRolesPut",++ "description": "network.machine-roles-put@1: the roles the owner gives a machine (PUT /api/v1/machines/:id/roles, capability machines.manage). The whole set, not a change: what is not named is turned off. Every id must be in the catalogue (network.machine-roles@1); an unknown role is refused and nothing is changed.",++ "type": "object",++ "required": ["roles"],++ "additionalProperties": false,++ "properties": {++ "roles": {++ "type": "array",++ "uniqueItems": true,++ "maxItems": 8,++ "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$" },++ "description": "The machine's roles after the change. An empty array turns every role off."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,17 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine-roles.v1.json",++ "title": "NetworkMachineRoles",++ "description": "network.machine-roles@1: the catalogue of machine roles (plan L34, step M4). A product declares a role — a name, what it installs on the machine and what the hub shows — and the Node turns it on at its next check-in. The first roles are Codes' worker, Desks' host, AI's local models and Bot's robot. A role outside this catalogue is refused (network.machine-roles-put@1).",++ "type": "object",++ "required": ["roles"],++ "additionalProperties": false,++ "properties": {++ "roles": {++ "type": "array",++ "minItems": 1,++ "uniqueItems": true,++ "items": { "$ref": "machine-role.v1.json" }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,50 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine.v1.json",++ "title": "NetworkMachine",++ "description": "network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Node install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.",++ "type": "object",++ "required": ["id", "kind", "online", "roles", "running"],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine's device id in Bot's registry (dev_…)."++ },++ "principal_id": {++ "type": "string",++ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine's node principal on Network (nod_…): the credential it holds. Absent for a machine Bot knows but Network has no principal for."++ },++ "name": { "type": "string", "maxLength": 80, "description": "The name the owner gave it. Absent when they gave none." },++ "kind": {++ "enum": ["onboard", "bridge", "server"],++ "description": "What the machine is, as Bot's device record says: onboard (the robot's own machine), bridge (a machine that drives a robot over the robot's own link), server (a machine that runs its owner's jobs)."++ },++ "os": { "type": "string", "maxLength": 40, "description": "The operating system the Node reports (linux, darwin, windows). Absent until the machine has checked in." },++ "online": { "type": "boolean", "description": "Whether the machine is connected to Bot right now." },++ "last_seen_at": { "type": "string", "format": "date-time", "description": "When the machine was last seen. Absent before its first check-in." },++ "node_version": { "type": "string", "maxLength": 40, "description": "The OpenVibe Node version the machine reports. Absent until it has checked in." },++ "roles": {++ "type": "array",++ "uniqueItems": true,++ "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$" },++ "description": "What the machine does for the network: the ids from the catalogue (network.machine-roles@1). The owner sets these; the machine applies them at its next check-in."++ },++ "running": {++ "type": "array",++ "description": "What the machine is doing right now, as a short list: its Codes jobs, its desks, the local models it serves and the robots it drives.",++ "items": {++ "type": "object",++ "required": ["kind", "count"],++ "additionalProperties": false,++ "properties": {++ "kind": { "enum": ["codes.jobs", "desks", "models", "robots"] },++ "count": { "type": "integer", "minimum": 0 },++ "label": { "type": "string", "maxLength": 120, "description": "A short, plain description for the owner (the desk's name, the model's name)." }++ }++ }++ }++ }++}@@@ -218,6 +218,37 @@"type": "string"}},++ "machineRoles": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "id",++ "name",++ "installs",++ "shows"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"++ },++ "name": {++ "type": "string",++ "maxLength": 60++ },++ "installs": {++ "type": "string",++ "maxLength": 200++ },++ "shows": {++ "type": "string",++ "maxLength": 200++ }++ }++ }++ },"contractRanges": {"type": "object","additionalProperties": {nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "roles": [++ "codesworker"++ ]++}nnew file mode 100644@@@ -0,0 +1 @@++{}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "nickname": "x"++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "name": "Kitchen server"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "roles": [++ "codes.worker",++ "ai.host"++ ]++}nnew file mode 100644@@@ -0,0 +1 @@++{}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "machine": {++ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "robot_ids": [],++ "kind": "server",++ "name": "Kitchen server",++ "agent_version": "0.15.0",++ "drivers": [],++ "capabilities": {},++ "last_seen": "2026-10-31T09:00:00.000Z",++ "revoked_at": null,++ "created_at": "2026-10-31T09:00:00.000Z",++ "updated_at": "2026-10-31T09:00:00.000Z",++ "online": true,++ "roles": [++ "codes.worker"++ ],++ "os": "linux"++ }++}nnew file mode 100644@@@ -0,0 +1 @@++{}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "machines": {++ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS"++ }++}nnew file mode 100644@@@ -0,0 +1,29 @@++{++ "machines": [++ {++ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "name": "Kitchen server",++ "kind": "server",++ "os": "linux",++ "online": true,++ "last_seen_at": "2026-10-31T09:00:00.000Z",++ "node_version": "0.15.0",++ "roles": [++ "codes.worker"++ ],++ "running": [++ {++ "kind": "codes.jobs",++ "count": 1++ }++ ]++ },++ {++ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "kind": "onboard",++ "online": false,++ "roles": [],++ "running": []++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,9 @@++{++ "machine": {++ "id": "x",++ "kind": "server",++ "online": false,++ "roles": [],++ "running": []++ }++}nnew file mode 100644@@@ -0,0 +1 @@++{}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "machine": {++ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "name": "Kitchen server",++ "kind": "server",++ "os": "linux",++ "online": true,++ "last_seen_at": "2026-10-31T09:00:00.000Z",++ "node_version": "0.15.0",++ "roles": [++ "codes.worker"++ ],++ "running": [++ {++ "kind": "codes.jobs",++ "count": 1++ }++ ]++ }++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "code": "abcd1234",++ "expires_at": "2026-10-31T09:00:00.000Z",++ "installer": "x"++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "code": "ABCD-1234",++ "expires_at": "2026-10-31T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "code": "ABCD-1234",++ "pairing_id": "pair_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "expires_at": "2026-10-31T09:00:00.000Z",++ "installer": "curl -fsSL https://openvibe.bot/install | sh -s -- --network https://openvibe.network --pairing pair_01JAB2C3D4E5F6G7H8J9K0MNPS --code ABCD-1234"++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "id": "codes.worker",++ "name": "Codes worker",++ "installs": "the Codes job runner"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "id": "worker",++ "name": "Codes worker",++ "installs": "x",++ "shows": "y"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "id": "codes.worker",++ "name": "Codes worker",++ "installs": "the Codes job runner",++ "shows": "Runs your coding jobs"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "roles": [++ "codes.worker",++ "codes.worker"++ ]++}nnew file mode 100644@@@ -0,0 +1 @@++{}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "roles": "codes.worker"++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "roles": []++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "roles": [++ "codes.worker",++ "ai.host"++ ]++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "roles": []++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "roles": "codes.worker"++}nnew file mode 100644@@@ -0,0 +1,28 @@++{++ "roles": [++ {++ "id": "codes.worker",++ "name": "Codes worker",++ "installs": "Codes worker on the machine",++ "shows": "Runs codes worker for you"++ },++ {++ "id": "desks.host",++ "name": "Desk host",++ "installs": "Desk host on the machine",++ "shows": "Runs desk host for you"++ },++ {++ "id": "ai.host",++ "name": "AI host",++ "installs": "AI host on the machine",++ "shows": "Runs ai host for you"++ },++ {++ "id": "bot.robot",++ "name": "Robot",++ "installs": "Robot on the machine",++ "shows": "Runs robot for you"++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "owner": "usr_x",++ "at": "2026-10-31T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "at": "2026-10-31T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "machine_id": "dev_x",++ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "at": "2026-10-31T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "at": "2026-10-31T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "at": "2026-10-31T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "node_id": "n-01jab2c3d4e5f6g7h8j9k0mnps",++ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "at": "2026-10-31T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "at": "2026-10-31T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "reason": "the owner removed it",++ "at": "2026-10-31T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "roles": [++ "nope"++ ],++ "at": "2026-10-31T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "roles": [++ "codes.worker",++ "ai.host"++ ],++ "changed_by": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "at": "2026-10-31T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "id": "machine-1",++ "kind": "server",++ "online": false,++ "roles": [],++ "running": []++}nnew file mode 100644@@@ -0,0 +1,9 @@++{++ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "kind": "server",++ "online": false,++ "roles": [++ "codesworker"++ ],++ "running": []++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "kind": "server",++ "online": false,++ "roles": [],++ "running": [],++ "last_seen_at": "yesterday"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "kind": "server",++ "online": false,++ "roles": []++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "kind": "laptop",++ "online": false,++ "roles": [],++ "running": []++}nnew file mode 100644@@@ -0,0 +1,9 @@++{++ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "kind": "onboard",++ "online": false,++ "roles": [++ "bot.robot"++ ],++ "running": []++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "name": "Kitchen server",++ "kind": "server",++ "os": "linux",++ "online": true,++ "last_seen_at": "2026-10-31T09:00:00.000Z",++ "node_version": "0.15.0",++ "roles": [++ "codes.worker"++ ],++ "running": [++ {++ "kind": "codes.jobs",++ "count": 2++ },++ {++ "kind": "models",++ "count": 1,++ "label": "llama3"++ }++ ]++}@@@ -939,6 +939,37 @@"type": "string"}},++ "machineRoles": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "id",++ "name",++ "installs",++ "shows"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"++ },++ "name": {++ "type": "string",++ "maxLength": 60++ },++ "installs": {++ "type": "string",++ "maxLength": 200++ },++ "shows": {++ "type": "string",++ "maxLength": 200++ }++ }++ }++ },"contractRanges": {"type": "object","additionalProperties": {@@@ -67245,6 +67276,566 @@}}},++ "network.machine": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine.v1.json",++ "title": "NetworkMachine",++ "description": "network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Node install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.",++ "type": "object",++ "required": [++ "id",++ "kind",++ "online",++ "roles",++ "running"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine's device id in Bot's registry (dev_…)."++ },++ "principal_id": {++ "type": "string",++ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine's node principal on Network (nod_…): the credential it holds. Absent for a machine Bot knows but Network has no principal for."++ },++ "name": {++ "type": "string",++ "maxLength": 80,++ "description": "The name the owner gave it. Absent when they gave none."++ },++ "kind": {++ "enum": [++ "onboard",++ "bridge",++ "server"++ ],++ "description": "What the machine is, as Bot's device record says: onboard (the robot's own machine), bridge (a machine that drives a robot over the robot's own link), server (a machine that runs its owner's jobs)."++ },++ "os": {++ "type": "string",++ "maxLength": 40,++ "description": "The operating system the Node reports (linux, darwin, windows). Absent until the machine has checked in."++ },++ "online": {++ "type": "boolean",++ "description": "Whether the machine is connected to Bot right now."++ },++ "last_seen_at": {++ "type": "string",++ "format": "date-time",++ "description": "When the machine was last seen. Absent before its first check-in."++ },++ "node_version": {++ "type": "string",++ "maxLength": 40,++ "description": "The OpenVibe Node version the machine reports. Absent until it has checked in."++ },++ "roles": {++ "type": "array",++ "uniqueItems": true,++ "items": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"++ },++ "description": "What the machine does for the network: the ids from the catalogue (network.machine-roles@1). The owner sets these; the machine applies them at its next check-in."++ },++ "running": {++ "type": "array",++ "description": "What the machine is doing right now, as a short list: its Codes jobs, its desks, the local models it serves and the robots it drives.",++ "items": {++ "type": "object",++ "required": [++ "kind",++ "count"++ ],++ "additionalProperties": false,++ "properties": {++ "kind": {++ "enum": [++ "codes.jobs",++ "desks",++ "models",++ "robots"++ ]++ },++ "count": {++ "type": "integer",++ "minimum": 0++ },++ "label": {++ "type": "string",++ "maxLength": 120,++ "description": "A short, plain description for the owner (the desk's name, the model's name)."++ }++ }++ }++ }++ }++ }++ },++ "network.machine-list-result": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine-list-result.v1.json",++ "title": "NetworkMachineListResult",++ "description": "network.machine-list-result@1: a person's machines, as the network's hub lists them (plan L34, step M2: GET /api/v1/machines). Only the caller's own machines are ever in it.",++ "type": "object",++ "required": [++ "machines"++ ],++ "additionalProperties": false,++ "properties": {++ "machines": {++ "type": "array",++ "items": {++ "$ref": "machine.v1.json"++ }++ }++ }++ }++ },++ "network.machine-manage-result": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine-manage-result.v1.json",++ "title": "NetworkMachineManageResult",++ "description": "network.machine-manage-result@1: the machine as it is after a change (plan L34, step M2: PATCH /api/v1/machines/:id, PUT /api/v1/machines/:id/roles, POST /api/v1/machines/:id/revoke). The pairing route answers network.machine-pairing@1 instead, because a pairing is a code and not a machine.",++ "type": "object",++ "required": [++ "machine"++ ],++ "additionalProperties": false,++ "properties": {++ "machine": {++ "$ref": "machine.v1.json"++ }++ }++ }++ },++ "bot.machine-manage-request": {++ "version": "1.0.0",++ "owner": "bot",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/bot/machine-manage-request.v1.json",++ "title": "BotMachineManageRequest",++ "description": "bot.machine-manage-request@1: what a caller changes on a machine (plan L34, step M2). A rename gives a name; a roles change gives the whole set of roles (network.machine-roles-put@1's shape). The same body is what the network's hub sends to Bot, so both sides speak one shape.",++ "type": "object",++ "additionalProperties": false,++ "anyOf": [++ {++ "required": [++ "name"++ ]++ },++ {++ "required": [++ "roles"++ ]++ }++ ],++ "properties": {++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80,++ "description": "The name the owner gives the machine. Absent when the call changes something else."++ },++ "roles": {++ "type": "array",++ "uniqueItems": true,++ "maxItems": 8,++ "items": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"++ },++ "description": "The machine's roles after the change. Absent when the call changes something else."++ }++ }++ }++ },++ "bot.machine-manage-result": {++ "version": "1.0.0",++ "owner": "bot",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/bot/machine-manage-result.v1.json",++ "title": "BotMachineManageResult",++ "description": "bot.machine-manage-result@1: the machine as it is after the change (plan L34, step M2). Bot is the registry underneath: it answers with its own device record, never with a credential.",++ "type": "object",++ "required": [++ "machine"++ ],++ "additionalProperties": false,++ "properties": {++ "machine": {++ "$ref": "device.v1.json"++ }++ }++ }++ },++ "network.machine-role": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine-role.v1.json",++ "title": "NetworkMachineRole",++ "description": "network.machine-role@1: one role a machine can have (plan L34, step M4). A role is what the machine does for the network: a name for the owner, what it installs on the machine and what the hub shows. The catalogue is network.machine-roles@1; a role outside it is refused.",++ "type": "object",++ "required": [++ "id",++ "name",++ "installs",++ "shows"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$",++ "description": "The role's id: the product that owns it, then what it is (codes.worker, desks.host, ai.host, bot.robot)."++ },++ "name": {++ "type": "string",++ "maxLength": 60,++ "description": "The role's name, in the owner's words."++ },++ "installs": {++ "type": "string",++ "maxLength": 200,++ "description": "What the role puts on the machine (the job runner, the desk sandbox, the local model server)."++ },++ "shows": {++ "type": "string",++ "maxLength": 200,++ "description": "What the hub shows for a machine with this role."++ }++ }++ }++ },++ "network.machine-roles": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine-roles.v1.json",++ "title": "NetworkMachineRoles",++ "description": "network.machine-roles@1: the catalogue of machine roles (plan L34, step M4). A product declares a role — a name, what it installs on the machine and what the hub shows — and the Node turns it on at its next check-in. The first roles are Codes' worker, Desks' host, AI's local models and Bot's robot. A role outside this catalogue is refused (network.machine-roles-put@1).",++ "type": "object",++ "required": [++ "roles"++ ],++ "additionalProperties": false,++ "properties": {++ "roles": {++ "type": "array",++ "minItems": 1,++ "uniqueItems": true,++ "items": {++ "$ref": "machine-role.v1.json"++ }++ }++ }++ }++ },++ "network.machine-roles-put": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine-roles-put.v1.json",++ "title": "NetworkMachineRolesPut",++ "description": "network.machine-roles-put@1: the roles the owner gives a machine (PUT /api/v1/machines/:id/roles, capability machines.manage). The whole set, not a change: what is not named is turned off. Every id must be in the catalogue (network.machine-roles@1); an unknown role is refused and nothing is changed.",++ "type": "object",++ "required": [++ "roles"++ ],++ "additionalProperties": false,++ "properties": {++ "roles": {++ "type": "array",++ "uniqueItems": true,++ "maxItems": 8,++ "items": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"++ },++ "description": "The machine's roles after the change. An empty array turns every role off."++ }++ }++ }++ },++ "network.machine-pairing": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/machine-pairing.v1.json",++ "title": "NetworkMachinePairing",++ "description": "network.machine-pairing@1: a one-time code that pairs a machine with the person's account (POST /api/v1/machines/pairings, capability machines.manage). The code is shown once, expires, and works once: the machine redeems it and becomes the person's. The command is what the owner runs on the machine (openvibe.bot/install). The code is never stored in the clear and never returned again.",++ "type": "object",++ "required": [++ "code",++ "expires_at",++ "installer"++ ],++ "additionalProperties": false,++ "properties": {++ "code": {++ "type": "string",++ "pattern": "^[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}$",++ "description": "The one-time pairing code, eight Crockford base32 characters in two groups (XXXX-XXXX)."++ },++ "pairing_id": {++ "type": "string",++ "pattern": "^pair_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The pairing's id, as the install command carries it. Absent when the authority behind it does not name one."++ },++ "expires_at": {++ "type": "string",++ "format": "date-time",++ "description": "When the code stops working."++ },++ "installer": {++ "type": "string",++ "maxLength": 500,++ "description": "The command the owner runs on the machine (openvibe.bot/install, with the network, the pairing id and the code)."++ }++ }++ }++ },++ "network.machine.online": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.online.v1.json",++ "title": "NetworkMachineOnlinePayload",++ "description": "network.machine.online v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person (a Bot device of kind machine) came online: it checked in and is connected to Bot. Network republishes Bot's change (Bot reports it to Network, which writes this event), so a consumer of the network's events hears about a machine once, in one place. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",++ "type": "object",++ "required": [++ "machine_id",++ "owner",++ "at"++ ],++ "additionalProperties": false,++ "properties": {++ "machine_id": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "principal_id": {++ "type": "string",++ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine's node principal on Network, when it has one."++ },++ "owner": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person the machine belongs to."++ },++ "at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "network.machine.offline": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.offline.v1.json",++ "title": "NetworkMachineOfflinePayload",++ "description": "network.machine.offline v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person went offline: it is no longer connected to Bot. Network republishes Bot's change, so a consumer of the network's events hears about a machine once, in one place. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",++ "type": "object",++ "required": [++ "machine_id",++ "owner",++ "at"++ ],++ "additionalProperties": false,++ "properties": {++ "machine_id": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "principal_id": {++ "type": "string",++ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine's node principal on Network, when it has one."++ },++ "owner": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person the machine belongs to."++ },++ "at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "network.machine.paired": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.paired.v1.json",++ "title": "NetworkMachinePairedPayload",++ "description": "network.machine.paired v1 (OpenVibe.Network, plan L34: the machines hub). A machine was paired with a person's account: it redeemed the one-time code and became theirs. It holds a credential from then on; the code is spent. The machine's device id in Bot is absent until the machine checks in for the first time. Envelope: subject { type: machine, id: <nod_…> }, visibility internal, actor service:network.",++ "type": "object",++ "required": [++ "principal_id",++ "owner",++ "at"++ ],++ "additionalProperties": false,++ "properties": {++ "machine_id": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine's device id in Bot's registry, when Bot already knows the device."++ },++ "node_id": {++ "type": "string",++ "pattern": "^n-[a-z0-9]{26}$",++ "description": "The machine's node id (platform_node_principals.node_id)."++ },++ "principal_id": {++ "type": "string",++ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The node principal the pairing created. Also the envelope subject id."++ },++ "owner": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person the machine belongs to."++ },++ "at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "network.machine.roles_changed": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.roles_changed.v1.json",++ "title": "NetworkMachineRolesChangedPayload",++ "description": "network.machine.roles_changed v1 (OpenVibe.Network, plan L34, step M4). A machine's roles changed: the whole set after the change, from the catalogue (network.machine-roles@1). The machine applies them at its next check-in, so this event says what was asked for, not what the machine has already done. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",++ "type": "object",++ "required": [++ "machine_id",++ "owner",++ "roles",++ "at"++ ],++ "additionalProperties": false,++ "properties": {++ "machine_id": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "owner": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person the machine belongs to."++ },++ "roles": {++ "type": "array",++ "uniqueItems": true,++ "items": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"++ },++ "description": "The machine's roles after the change."++ },++ "changed_by": {++ "type": "string",++ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|app:[a-z0-9-]+)$",++ "description": "Who asked for the change: the person, or an app acting with their consent."++ },++ "at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "network.machine.revoked": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.revoked.v1.json",++ "title": "NetworkMachineRevokedPayload",++ "description": "network.machine.revoked v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person was revoked: it is no longer theirs and its credential is dead. The machine's credential and its hash are never carried. A second revoke changes nothing and emits nothing. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",++ "type": "object",++ "required": [++ "machine_id",++ "owner",++ "at"++ ],++ "additionalProperties": false,++ "properties": {++ "machine_id": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "principal_id": {++ "type": "string",++ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine's node principal on Network, when it had one."++ },++ "owner": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person the machine belonged to."++ },++ "reason": {++ "type": "string",++ "minLength": 1,++ "maxLength": 500,++ "description": "Why it was revoked, when the caller gave one. Absent otherwise."++ },++ "at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },"network.avatar-write-request": {"version": "1.0.0","owner": "network",@@@ -72552,6 +73143,21 @@},"online": {"type": "boolean"++ },++ "roles": {++ "type": "array",++ "items": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"++ },++ "description": "The machine roles the owner set (plan L34: the catalogue is network.machine-roles@1). Absent on a device that has none."++ },++ "os": {++ "type": [++ "string",++ "null"++ ],++ "description": "The operating system the device reported."}}}@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Actor API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.AI API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Bench API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Billing API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Blog API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Bot API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -472,6 +472,178 @@}}},++ "/api/v1/machines": {++ "get": {++ "operationId": "get_api_v1_machines",++ "summary": "A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject).",++ "description": "**bot.machine.manage** (first-party): A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject). Network holds it for the machines hub (openvibe.network/machines): Bot stays the registry underneath, so Bot checks that the machine is the named person's own. Never grantable to apps.",++ "tags": [++ "bot.machine"++ ],++ "x-openvibe-capabilities": [++ "bot.machine.manage"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "bot-machine",++ "security": [++ {++ "openvibeToken": [++ "bot.machine.manage"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/bot.machine-manage-request.v1"++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.machine-manage-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/machines/{id}": {++ "get": {++ "operationId": "get_api_v1_machines_id",++ "summary": "A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject).",++ "description": "**bot.machine.manage** (first-party): A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject). Network holds it for the machines hub (openvibe.network/machines): Bot stays the registry underneath, so Bot checks that the machine is the named person's own. Never grantable to apps.",++ "tags": [++ "bot.machine"++ ],++ "x-openvibe-capabilities": [++ "bot.machine.manage"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "bot-machine",++ "security": [++ {++ "openvibeToken": [++ "bot.machine.manage"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/bot.machine-manage-request.v1"++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.machine-manage-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ },++ "patch": {++ "operationId": "patch_api_v1_machines_id",++ "summary": "A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject).",++ "description": "**bot.machine.manage** (first-party): A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject). Network holds it for the machines hub (openvibe.network/machines): Bot stays the registry underneath, so Bot checks that the machine is the named person's own. Never grantable to apps.",++ "tags": [++ "bot.machine"++ ],++ "x-openvibe-capabilities": [++ "bot.machine.manage"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "bot-machine",++ "security": [++ {++ "openvibeToken": [++ "bot.machine.manage"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.machine-manage-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.machine-manage-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },"/api/v1/machines/{id}/desk-leases": {"post": {"operationId": "post_api_v1_machines_id_desk_leases",@@@ -816,6 +988,138 @@}}},++ "/api/v1/machines/{id}/revoke": {++ "post": {++ "operationId": "post_api_v1_machines_id_revoke",++ "summary": "A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject).",++ "description": "**bot.machine.manage** (first-party): A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject). Network holds it for the machines hub (openvibe.network/machines): Bot stays the registry underneath, so Bot checks that the machine is the named person's own. Never grantable to apps.",++ "tags": [++ "bot.machine"++ ],++ "x-openvibe-capabilities": [++ "bot.machine.manage"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "bot-machine",++ "security": [++ {++ "openvibeToken": [++ "bot.machine.manage"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.machine-manage-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.machine-manage-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/machines/{id}/roles": {++ "put": {++ "operationId": "put_api_v1_machines_id_roles",++ "summary": "A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject).",++ "description": "**bot.machine.manage** (first-party): A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject). Network holds it for the machines hub (openvibe.network/machines): Bot stays the registry underneath, so Bot checks that the machine is the named person's own. Never grantable to apps.",++ "tags": [++ "bot.machine"++ ],++ "x-openvibe-capabilities": [++ "bot.machine.manage"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "bot-machine",++ "security": [++ {++ "openvibeToken": [++ "bot.machine.manage"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.machine-manage-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.machine-manage-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },"/api/v1/resources": {"get": {"operationId": "get_api_v1_resources",@@@ -2092,6 +2396,21 @@},"online": {"type": "boolean"++ },++ "roles": {++ "type": "array",++ "items": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"++ },++ "description": "The machine roles the owner set (plan L34: the catalogue is network.machine-roles@1). Absent on a device that has none."++ },++ "os": {++ "type": [++ "string",++ "null"++ ],++ "description": "The operating system the device reported."}}},@@@ -2356,6 +2675,56 @@}}},++ "bot.machine-manage-request.v1": {++ "title": "BotMachineManageRequest",++ "description": "bot.machine-manage-request@1: what a caller changes on a machine (plan L34, step M2). A rename gives a name; a roles change gives the whole set of roles (network.machine-roles-put@1's shape). The same body is what the network's hub sends to Bot, so both sides speak one shape.",++ "type": "object",++ "additionalProperties": false,++ "anyOf": [++ {++ "required": [++ "name"++ ]++ },++ {++ "required": [++ "roles"++ ]++ }++ ],++ "properties": {++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80,++ "description": "The name the owner gives the machine. Absent when the call changes something else."++ },++ "roles": {++ "type": "array",++ "uniqueItems": true,++ "maxItems": 8,++ "items": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"++ },++ "description": "The machine's roles after the change. Absent when the call changes something else."++ }++ }++ },++ "bot.machine-manage-result.v1": {++ "title": "BotMachineManageResult",++ "description": "bot.machine-manage-result@1: the machine as it is after the change (plan L34, step M2). Bot is the registry underneath: it answers with its own device record, never with a credential.",++ "type": "object",++ "required": [++ "machine"++ ],++ "additionalProperties": false,++ "properties": {++ "machine": {++ "$ref": "#/components/schemas/bot.device.v1"++ }++ }++ },"bot.robot-control-result.v1": {"title": "BotRobotControlResult","description": "bot.robot-control-result@1: answers of bot.robot.control on OpenVibe.Bot. POST /api/v1/robots/:id/estop → { robot } with its e-stop as it now stands.",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Chat API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Codes API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Community API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Coupons API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Deals API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Domains API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Events API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Food API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Games API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Host API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -40,8 +40,8 @@"service": "bot","name": "OpenVibe.Bot","origin": "https://openvibe.bot",-- "operations": 25,-- "capabilities": 9++ "operations": 30,++ "capabilities": 10},{"service": "chat",@@@ -145,8 +145,8 @@"service": "network","name": "OpenVibe.Network","origin": "https://openvibe.network",-- "operations": 47,-- "capabilities": 32++ "operations": 54,++ "capabilities": 34},{"service": "news",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Inventory API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Live API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.MediaHub API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Media API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Network API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -136,6 +136,406 @@}}},++ "/api/v1/machines": {++ "get": {++ "operationId": "get_api_v1_machines",++ "summary": "Read a person's own machines: the list, one machine, and the catalogue of roles.",++ "description": "**network.machines.read** (public): Read a person's own machines: the list, one machine, and the catalogue of roles. A person reads their own with their own token; an app needs the person's consent for this scope (it is a public capability, so it can be granted to an app, and the app then acts only on behalf of the person who consented).",++ "tags": [++ "network.machines"++ ],++ "x-openvibe-capabilities": [++ "network.machines.read"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "network-read",++ "security": [++ {++ "openvibeToken": [++ "network.machines.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.machine-list-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/machines/{id}": {++ "get": {++ "operationId": "get_api_v1_machines_id",++ "summary": "Read a person's own machines: the list, one machine, and the catalogue of roles.",++ "description": "**network.machines.read** (public): Read a person's own machines: the list, one machine, and the catalogue of roles. A person reads their own with their own token; an app needs the person's consent for this scope (it is a public capability, so it can be granted to an app, and the app then acts only on behalf of the person who consented).",++ "tags": [++ "network.machines"++ ],++ "x-openvibe-capabilities": [++ "network.machines.read"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "network-read",++ "security": [++ {++ "openvibeToken": [++ "network.machines.read"++ ]++ }++ ],++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.machine-list-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ },++ "patch": {++ "operationId": "patch_api_v1_machines_id",++ "summary": "Pair, rename, give roles to and revoke a person's own machines.",++ "description": "**network.machines.manage** (public): Pair, rename, give roles to and revoke a person's own machines. Bot stays the registry underneath: Network asks Bot as the person, and Bot checks that the machine is theirs. An app needs the person's consent for this scope, and may then do only what the person could.",++ "tags": [++ "network.machines"++ ],++ "x-openvibe-capabilities": [++ "network.machines.manage"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "network-write",++ "security": [++ {++ "openvibeToken": [++ "network.machines.manage"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.machine-manage-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.machine-manage-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/machines/{id}/revoke": {++ "post": {++ "operationId": "post_api_v1_machines_id_revoke",++ "summary": "Pair, rename, give roles to and revoke a person's own machines.",++ "description": "**network.machines.manage** (public): Pair, rename, give roles to and revoke a person's own machines. Bot stays the registry underneath: Network asks Bot as the person, and Bot checks that the machine is theirs. An app needs the person's consent for this scope, and may then do only what the person could.",++ "tags": [++ "network.machines"++ ],++ "x-openvibe-capabilities": [++ "network.machines.manage"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "network-write",++ "security": [++ {++ "openvibeToken": [++ "network.machines.manage"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.machine-manage-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.machine-manage-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/machines/{id}/roles": {++ "put": {++ "operationId": "put_api_v1_machines_id_roles",++ "summary": "Pair, rename, give roles to and revoke a person's own machines.",++ "description": "**network.machines.manage** (public): Pair, rename, give roles to and revoke a person's own machines. Bot stays the registry underneath: Network asks Bot as the person, and Bot checks that the machine is theirs. An app needs the person's consent for this scope, and may then do only what the person could.",++ "tags": [++ "network.machines"++ ],++ "x-openvibe-capabilities": [++ "network.machines.manage"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "network-write",++ "security": [++ {++ "openvibeToken": [++ "network.machines.manage"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.machine-manage-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.machine-manage-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/machines/pairings": {++ "post": {++ "operationId": "post_api_v1_machines_pairings",++ "summary": "Pair, rename, give roles to and revoke a person's own machines.",++ "description": "**network.machines.manage** (public): Pair, rename, give roles to and revoke a person's own machines. Bot stays the registry underneath: Network asks Bot as the person, and Bot checks that the machine is theirs. An app needs the person's consent for this scope, and may then do only what the person could.",++ "tags": [++ "network.machines"++ ],++ "x-openvibe-capabilities": [++ "network.machines.manage"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "network-write",++ "security": [++ {++ "openvibeToken": [++ "network.machines.manage"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.machine-manage-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.machine-manage-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/machines/roles": {++ "get": {++ "operationId": "get_api_v1_machines_roles",++ "summary": "Read a person's own machines: the list, one machine, and the catalogue of roles.",++ "description": "**network.machines.read** (public): Read a person's own machines: the list, one machine, and the catalogue of roles. A person reads their own with their own token; an app needs the person's consent for this scope (it is a public capability, so it can be granted to an app, and the app then acts only on behalf of the person who consented).",++ "tags": [++ "network.machines"++ ],++ "x-openvibe-capabilities": [++ "network.machines.read"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "network-read",++ "security": [++ {++ "openvibeToken": [++ "network.machines.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.machine-list-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },"/api/v1/me/actions": {"get": {"operationId": "get_api_v1_me_actions",@@@ -2898,6 +3298,42 @@},"components": {"schemas": {++ "bot.machine-manage-request.v1": {++ "title": "BotMachineManageRequest",++ "description": "bot.machine-manage-request@1: what a caller changes on a machine (plan L34, step M2). A rename gives a name; a roles change gives the whole set of roles (network.machine-roles-put@1's shape). The same body is what the network's hub sends to Bot, so both sides speak one shape.",++ "type": "object",++ "additionalProperties": false,++ "anyOf": [++ {++ "required": [++ "name"++ ]++ },++ {++ "required": [++ "roles"++ ]++ }++ ],++ "properties": {++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80,++ "description": "The name the owner gives the machine. Absent when the call changes something else."++ },++ "roles": {++ "type": "array",++ "uniqueItems": true,++ "maxItems": 8,++ "items": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"++ },++ "description": "The machine's roles after the change. Absent when the call changes something else."++ }++ }++ },"common.action-item.v1": {"title": "ActionItem","description": "One piece of a person's agent work in the Action Center (plan 2026-10-28 §3.10): an agent task, a coding task, an AI run, or an approval waiting on them. Each authority answers its own items in this shape (common.action-list-result@1); OpenVibe.Network merges them into buckets (network.me-actions-result@1). Text is plain (no markup); a cost is what the person was charged in USD, null when unknown or free; href is a page on the authority's own public site.",@@@ -4608,6 +5044,134 @@}}},++ "network.machine-list-result.v1": {++ "title": "NetworkMachineListResult",++ "description": "network.machine-list-result@1: a person's machines, as the network's hub lists them (plan L34, step M2: GET /api/v1/machines). Only the caller's own machines are ever in it.",++ "type": "object",++ "required": [++ "machines"++ ],++ "additionalProperties": false,++ "properties": {++ "machines": {++ "type": "array",++ "items": {++ "$ref": "#/components/schemas/network.machine.v1"++ }++ }++ }++ },++ "network.machine-manage-result.v1": {++ "title": "NetworkMachineManageResult",++ "description": "network.machine-manage-result@1: the machine as it is after a change (plan L34, step M2: PATCH /api/v1/machines/:id, PUT /api/v1/machines/:id/roles, POST /api/v1/machines/:id/revoke). The pairing route answers network.machine-pairing@1 instead, because a pairing is a code and not a machine.",++ "type": "object",++ "required": [++ "machine"++ ],++ "additionalProperties": false,++ "properties": {++ "machine": {++ "$ref": "#/components/schemas/network.machine.v1"++ }++ }++ },++ "network.machine.v1": {++ "title": "NetworkMachine",++ "description": "network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Node install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.",++ "type": "object",++ "required": [++ "id",++ "kind",++ "online",++ "roles",++ "running"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine's device id in Bot's registry (dev_…)."++ },++ "principal_id": {++ "type": "string",++ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine's node principal on Network (nod_…): the credential it holds. Absent for a machine Bot knows but Network has no principal for."++ },++ "name": {++ "type": "string",++ "maxLength": 80,++ "description": "The name the owner gave it. Absent when they gave none."++ },++ "kind": {++ "enum": [++ "onboard",++ "bridge",++ "server"++ ],++ "description": "What the machine is, as Bot's device record says: onboard (the robot's own machine), bridge (a machine that drives a robot over the robot's own link), server (a machine that runs its owner's jobs)."++ },++ "os": {++ "type": "string",++ "maxLength": 40,++ "description": "The operating system the Node reports (linux, darwin, windows). Absent until the machine has checked in."++ },++ "online": {++ "type": "boolean",++ "description": "Whether the machine is connected to Bot right now."++ },++ "last_seen_at": {++ "type": "string",++ "format": "date-time",++ "description": "When the machine was last seen. Absent before its first check-in."++ },++ "node_version": {++ "type": "string",++ "maxLength": 40,++ "description": "The OpenVibe Node version the machine reports. Absent until it has checked in."++ },++ "roles": {++ "type": "array",++ "uniqueItems": true,++ "items": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"++ },++ "description": "What the machine does for the network: the ids from the catalogue (network.machine-roles@1). The owner sets these; the machine applies them at its next check-in."++ },++ "running": {++ "type": "array",++ "description": "What the machine is doing right now, as a short list: its Codes jobs, its desks, the local models it serves and the robots it drives.",++ "items": {++ "type": "object",++ "required": [++ "kind",++ "count"++ ],++ "additionalProperties": false,++ "properties": {++ "kind": {++ "enum": [++ "codes.jobs",++ "desks",++ "models",++ "robots"++ ]++ },++ "count": {++ "type": "integer",++ "minimum": 0++ },++ "label": {++ "type": "string",++ "maxLength": 120,++ "description": "A short, plain description for the owner (the desk's name, the model's name)."++ }++ }++ }++ }++ }++ },"network.me-actions-result.v1": {"title": "MeActionsResult","description": "The Action Center for the signed-in person (capability network.actions.read, GET /api/v1/me/actions): every authority's items merged into five buckets, newest first in each. sources says how each authority answered; a slow or failing one is left out (partial true), never fails the answer.",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.News API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenRestream API",-- "version": "0.260.0",-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Play API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Quest API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Report API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Reviews API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Run API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Search API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Services API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Sources API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Space API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tips API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tools API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Trade API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.VIP API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Vision API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Watch API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Wiki API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Work API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Zone API",-- "version": "0.260.0",-- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.261.0",++ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -2042,6 +2042,12 @@ export interface ServiceManifest {eventsProduced: string[];eventsConsumed: string[];namespacesOwned: string[];++ machineRoles?: {++ id: string;++ name: string;++ installs: string;++ shows: string;++ }[];contractRanges?: {[k: string]: string | undefined;};@@@ -201099,6 +201105,291 @@ export interface NetworkNodeListResult {generated_at: string;}++/** [email protected] (owner: network) */++/**++ * network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Node install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.++ */++export interface NetworkMachine {++ /**++ * The machine's device id in Bot's registry (dev_…).++ */++ id: string;++ /**++ * The machine's node principal on Network (nod_…): the credential it holds. Absent for a machine Bot knows but Network has no principal for.++ */++ principal_id?: string;++ /**++ * The name the owner gave it. Absent when they gave none.++ */++ name?: string;++ /**++ * What the machine is, as Bot's device record says: onboard (the robot's own machine), bridge (a machine that drives a robot over the robot's own link), server (a machine that runs its owner's jobs).++ */++ kind: "onboard" | "bridge" | "server";++ /**++ * The operating system the Node reports (linux, darwin, windows). Absent until the machine has checked in.++ */++ os?: string;++ /**++ * Whether the machine is connected to Bot right now.++ */++ online: boolean;++ /**++ * When the machine was last seen. Absent before its first check-in.++ */++ last_seen_at?: string;++ /**++ * The OpenVibe Node version the machine reports. Absent until it has checked in.++ */++ node_version?: string;++ /**++ * What the machine does for the network: the ids from the catalogue (network.machine-roles@1). The owner sets these; the machine applies them at its next check-in.++ */++ roles: string[];++ /**++ * What the machine is doing right now, as a short list: its Codes jobs, its desks, the local models it serves and the robots it drives.++ */++ running: {++ kind: "codes.jobs" | "desks" | "models" | "robots";++ count: number;++ /**++ * A short, plain description for the owner (the desk's name, the model's name).++ */++ label?: string;++ }[];++}++++/** [email protected] (owner: network) */++/**++ * network.machine-list-result@1: a person's machines, as the network's hub lists them (plan L34, step M2: GET /api/v1/machines). Only the caller's own machines are ever in it.++ */++export interface NetworkMachineListResult {++ machines: NetworkMachine[];++}++++/** [email protected] (owner: network) */++/**++ * network.machine-manage-result@1: the machine as it is after a change (plan L34, step M2: PATCH /api/v1/machines/:id, PUT /api/v1/machines/:id/roles, POST /api/v1/machines/:id/revoke). The pairing route answers network.machine-pairing@1 instead, because a pairing is a code and not a machine.++ */++export interface NetworkMachineManageResult {++ machine: NetworkMachine;++}++++/** [email protected] (owner: bot) */++/**++ * bot.machine-manage-request@1: what a caller changes on a machine (plan L34, step M2). A rename gives a name; a roles change gives the whole set of roles (network.machine-roles-put@1's shape). The same body is what the network's hub sends to Bot, so both sides speak one shape.++ */++export type BotMachineManageRequest = {++ [k: string]: unknown | undefined;++} & {++ /**++ * The name the owner gives the machine. Absent when the call changes something else.++ */++ name?: string;++ /**++ * The machine's roles after the change. Absent when the call changes something else.++ *++ * @maxItems 8++ */++ roles?:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string];++};++++/** [email protected] (owner: bot) */++/**++ * bot.machine-manage-result@1: the machine as it is after the change (plan L34, step M2). Bot is the registry underneath: it answers with its own device record, never with a credential.++ */++export interface BotMachineManageResult {++ machine: BotDevice;++}++++/** [email protected] (owner: network) */++/**++ * network.machine-role@1: one role a machine can have (plan L34, step M4). A role is what the machine does for the network: a name for the owner, what it installs on the machine and what the hub shows. The catalogue is network.machine-roles@1; a role outside it is refused.++ */++export interface NetworkMachineRole {++ /**++ * The role's id: the product that owns it, then what it is (codes.worker, desks.host, ai.host, bot.robot).++ */++ id: string;++ /**++ * The role's name, in the owner's words.++ */++ name: string;++ /**++ * What the role puts on the machine (the job runner, the desk sandbox, the local model server).++ */++ installs: string;++ /**++ * What the hub shows for a machine with this role.++ */++ shows: string;++}++++/** [email protected] (owner: network) */++/**++ * network.machine-roles@1: the catalogue of machine roles (plan L34, step M4). A product declares a role — a name, what it installs on the machine and what the hub shows — and the Node turns it on at its next check-in. The first roles are Codes' worker, Desks' host, AI's local models and Bot's robot. A role outside this catalogue is refused (network.machine-roles-put@1).++ */++export interface NetworkMachineRoles {++ /**++ * @minItems 1++ */++ roles: [NetworkMachineRole, ...NetworkMachineRole[]];++}++++/** [email protected] (owner: network) */++/**++ * network.machine-roles-put@1: the roles the owner gives a machine (PUT /api/v1/machines/:id/roles, capability machines.manage). The whole set, not a change: what is not named is turned off. Every id must be in the catalogue (network.machine-roles@1); an unknown role is refused and nothing is changed.++ */++export interface NetworkMachineRolesPut {++ /**++ * The machine's roles after the change. An empty array turns every role off.++ *++ * @maxItems 8++ */++ roles:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string];++}++++/** [email protected] (owner: network) */++/**++ * network.machine-pairing@1: a one-time code that pairs a machine with the person's account (POST /api/v1/machines/pairings, capability machines.manage). The code is shown once, expires, and works once: the machine redeems it and becomes the person's. The command is what the owner runs on the machine (openvibe.bot/install). The code is never stored in the clear and never returned again.++ */++export interface NetworkMachinePairing {++ /**++ * The one-time pairing code, eight Crockford base32 characters in two groups (XXXX-XXXX).++ */++ code: string;++ /**++ * The pairing's id, as the install command carries it. Absent when the authority behind it does not name one.++ */++ pairing_id?: string;++ /**++ * When the code stops working.++ */++ expires_at: string;++ /**++ * The command the owner runs on the machine (openvibe.bot/install, with the network, the pairing id and the code).++ */++ installer: string;++}++++/** [email protected] (owner: network) */++/**++ * network.machine.online v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person (a Bot device of kind machine) came online: it checked in and is connected to Bot. Network republishes Bot's change (Bot reports it to Network, which writes this event), so a consumer of the network's events hears about a machine once, in one place. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.++ */++export interface NetworkMachineOnlinePayload {++ machine_id: string;++ /**++ * The machine's node principal on Network, when it has one.++ */++ principal_id?: string;++ /**++ * The person the machine belongs to.++ */++ owner: string;++ at: string;++}++++/** [email protected] (owner: network) */++/**++ * network.machine.offline v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person went offline: it is no longer connected to Bot. Network republishes Bot's change, so a consumer of the network's events hears about a machine once, in one place. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.++ */++export interface NetworkMachineOfflinePayload {++ machine_id: string;++ /**++ * The machine's node principal on Network, when it has one.++ */++ principal_id?: string;++ /**++ * The person the machine belongs to.++ */++ owner: string;++ at: string;++}++++/** [email protected] (owner: network) */++/**++ * network.machine.paired v1 (OpenVibe.Network, plan L34: the machines hub). A machine was paired with a person's account: it redeemed the one-time code and became theirs. It holds a credential from then on; the code is spent. The machine's device id in Bot is absent until the machine checks in for the first time. Envelope: subject { type: machine, id: <nod_…> }, visibility internal, actor service:network.++ */++export interface NetworkMachinePairedPayload {++ /**++ * The machine's device id in Bot's registry, when Bot already knows the device.++ */++ machine_id?: string;++ /**++ * The machine's node id (platform_node_principals.node_id).++ */++ node_id?: string;++ /**++ * The node principal the pairing created. Also the envelope subject id.++ */++ principal_id: string;++ /**++ * The person the machine belongs to.++ */++ owner: string;++ at: string;++}++++/** [email protected] (owner: network) */++/**++ * network.machine.roles_changed v1 (OpenVibe.Network, plan L34, step M4). A machine's roles changed: the whole set after the change, from the catalogue (network.machine-roles@1). The machine applies them at its next check-in, so this event says what was asked for, not what the machine has already done. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.++ */++export interface NetworkMachineRolesChangedPayload {++ machine_id: string;++ /**++ * The person the machine belongs to.++ */++ owner: string;++ /**++ * The machine's roles after the change.++ */++ roles: string[];++ /**++ * Who asked for the change: the person, or an app acting with their consent.++ */++ changed_by?: string;++ at: string;++}++++/** [email protected] (owner: network) */++/**++ * network.machine.revoked v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person was revoked: it is no longer theirs and its credential is dead. The machine's credential and its hash are never carried. A second revoke changes nothing and emits nothing. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.++ */++export interface NetworkMachineRevokedPayload {++ machine_id: string;++ /**++ * The machine's node principal on Network, when it had one.++ */++ principal_id?: string;++ /**++ * The person the machine belonged to.++ */++ owner: string;++ /**++ * Why it was revoked, when the caller gave one. Absent otherwise.++ */++ reason?: string;++ at: string;++}++/** [email protected] (owner: network) *//*** network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.@@@ -203351,6 +203642,14 @@ export interface BotDevice {created_at: string;updated_at: string;online?: boolean;++ /**++ * The machine roles the owner set (plan L34: the catalogue is network.machine-roles@1). Absent on a device that has none.++ */++ roles?: string[];++ /**++ * The operating system the device reported.++ */++ os?: string | null;}/** [email protected] (owner: bot) */nnew file mode 100644@@@ -0,0 +1,29 @@++{++ "id": "bot.machine.manage",++ "version": "1.0",++ "owner": "bot",++ "status": "active",++ "visibility": "first-party",++ "sensitive": true,++ "description": "A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject). Network holds it for the machines hub (openvibe.network/machines): Bot stays the registry underneath, so Bot checks that the machine is the named person's own. Never grantable to apps.",++ "permissions": [++ "bot:machine:read",++ "bot:machine:rename",++ "bot:machine:roles",++ "bot:machine:revoke"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "bot-machine",++ "events": [],++ "implementedBy": [++ "GET /api/v1/machines",++ "GET /api/v1/machines/:id",++ "PATCH /api/v1/machines/:id",++ "PUT /api/v1/machines/:id/roles",++ "POST /api/v1/machines/:id/revoke"++ ],++ "inputSchema": "bot.machine-manage-request@1",++ "outputSchema": "bot.machine-manage-result@1"++}nnew file mode 100644@@@ -0,0 +1,32 @@++{++ "id": "network.machines.manage",++ "version": "1.0",++ "owner": "network",++ "status": "active",++ "visibility": "public",++ "sensitive": true,++ "description": "Pair, rename, give roles to and revoke a person's own machines. Bot stays the registry underneath: Network asks Bot as the person, and Bot checks that the machine is theirs. An app needs the person's consent for this scope, and may then do only what the person could.",++ "permissions": [++ "machines:pair",++ "machines:rename",++ "machines:roles",++ "machines:revoke"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "network-write",++ "events": [++ "network.machine.paired",++ "network.machine.roles_changed",++ "network.machine.revoked"++ ],++ "implementedBy": [++ "POST /api/v1/machines/pairings",++ "PATCH /api/v1/machines/:id",++ "PUT /api/v1/machines/:id/roles",++ "POST /api/v1/machines/:id/revoke"++ ],++ "inputSchema": "bot.machine-manage-request@1",++ "outputSchema": "network.machine-manage-result@1"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "network.machines.read",++ "version": "1.0",++ "owner": "network",++ "status": "active",++ "visibility": "public",++ "description": "Read a person's own machines: the list, one machine, and the catalogue of roles. A person reads their own with their own token; an app needs the person's consent for this scope (it is a public capability, so it can be granted to an app, and the app then acts only on behalf of the person who consented).",++ "permissions": [++ "machines:read"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "network-read",++ "events": [],++ "implementedBy": [++ "GET /api/v1/machines",++ "GET /api/v1/machines/:id",++ "GET /api/v1/machines/roles"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "network.machine-list-result@1"++}@@@ -116,5 +116,13 @@}],"neighbourhood": "build"-- }++ },++ "machineRoles": [++ {++ "id": "ai.host",++ "name": "AI host",++ "installs": "the local model server (Ollama, LM Studio) as OpenVibe Node's ai plugin",++ "shows": "Serves your own AI models, on your own machine."++ }++ ]}@@@ -37,7 +37,8 @@"bot.job.dispatch","bot.resource.read","bot.resource.control",-- "bot.desk.session"++ "bot.desk.session",++ "bot.machine.manage"],"eventsProduced": ["bot.robot.online",@@@ -104,5 +105,13 @@"none": "no expiring work claims: PostgreSQL row locks serialize queue changes and an advisory lock serializes boot migrations; WebSocket sessions are in memory"}},-- "notes": "Robot and device control service on openvibe-ovh since 2026-10-02; the public site uses HTTPS while health and readiness are loopback-only. Non-HTTP bindings: the device WebSocket /device carries bot.device-message@1 frames (and platform.job-frame@1 jobs); the operator WebSocket /control carries bot.command@1, answered by bot.command-result@1."++ "notes": "Robot and device control service on openvibe-ovh since 2026-10-02; the public site uses HTTPS while health and readiness are loopback-only. Non-HTTP bindings: the device WebSocket /device carries bot.device-message@1 frames (and platform.job-frame@1 jobs); the operator WebSocket /control carries bot.command@1, answered by bot.command-result@1.",++ "machineRoles": [++ {++ "id": "bot.robot",++ "name": "Robot",++ "installs": "the robot drivers the profile asks for",++ "shows": "Drives a robot, and takes its commands."++ }++ ]}@@@ -109,5 +109,13 @@"none": "no claims: one process owns the ov_codes PostgreSQL database; writes are transactions there"}},-- "notes": "Port 4900, unit openvibe-codes.service. The open coding-agent harness (owner 2026-10-08): the harness catalog and router (GET /api/v1/harnesses, POST /api/v1/harnesses/route), the way to improve OpenVibe (contributing, code of conduct, the contributor ladder) and permanent redirects from the former developer-portal paths to openvibe.services, where the portal moved (manifests, releases, trust, playgrounds, docs, OAuth and webhook tools). Coding tasks on the person's own machine (plan T16) are Run jobs: their sessions are kept line by line in codes_session_lines as the stream arrives (back-filled from the machine's final log; GET /run/tasks/:id/session.json replays them), run.job.* events from OpenVibe.Run finalize a task without polling, and every state change is published as codes.task.queued|running|succeeded|failed|cancelled from a transactional outbox."++ "notes": "Port 4900, unit openvibe-codes.service. The open coding-agent harness (owner 2026-10-08): the harness catalog and router (GET /api/v1/harnesses, POST /api/v1/harnesses/route), the way to improve OpenVibe (contributing, code of conduct, the contributor ladder) and permanent redirects from the former developer-portal paths to openvibe.services, where the portal moved (manifests, releases, trust, playgrounds, docs, OAuth and webhook tools). Coding tasks on the person's own machine (plan T16) are Run jobs: their sessions are kept line by line in codes_session_lines as the stream arrives (back-filled from the machine's final log; GET /run/tasks/:id/session.json replays them), run.job.* events from OpenVibe.Run finalize a task without polling, and every state change is published as codes.task.queued|running|succeeded|failed|cancelled from a transactional outbox.",++ "machineRoles": [++ {++ "id": "codes.worker",++ "name": "Codes worker",++ "installs": "the Codes job runner (the coding agent and the job sandbox)",++ "shows": "Runs your coding jobs, such as a task you start on openvibe.codes."++ }++ ]}@@@ -80,7 +80,9 @@"network.status.incident","network.actions.read","network.consent.read",-- "network.consent.manage"++ "network.consent.manage",++ "network.machines.read",++ "network.machines.manage"],"eventsProduced": ["network.account.deleted",@@@ -102,7 +104,12 @@"network.subject.merged","network.user.token_valid_after","network.user.updated",-- "network.consent.changed"++ "network.consent.changed",++ "network.machine.online",++ "network.machine.offline",++ "network.machine.paired",++ "network.machine.roles_changed",++ "network.machine.revoked"],"eventsConsumed": ["billing.staff.action",@@@ -139,5 +139,13 @@],"keywords": "openvibe run, job runner, run jobs on nodes, sandbox api, per-second metering","neighbourhood": "build"-- }++ },++ "machineRoles": [++ {++ "id": "desks.host",++ "name": "Desk host",++ "installs": "the desk sandbox (a private browser desk for every session)",++ "shows": "Hosts desks for agents, yours and other people's."++ }++ ]}@@@ -1,12 +1,12 @@{"name": "openvibe-contracts",-- "version": "0.260.0",++ "version": "0.261.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-contracts",-- "version": "0.260.0",++ "version": "0.261.0","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.260.0",++ "version": "0.261.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",@@@ -12,7 +12,9 @@ const c = require('..');// bot.resource.control: an owner stops or resumes their robot from OpenVibe.Services (0.176.0, ADR-048 step 8 phase 2).// bot.machine.ai: a model on the person's own machine, for that person (0.219.0, the AI-on-your-machines plan).// bot.desk.session: Run's sessions lease one desk each (0.223.0, the Desks plan's D6).--const BOT_CAPABILITIES = ['bot.robot.read', 'bot.robot.manage', 'bot.robot.control', 'bot.computer.control', 'bot.machine.ai', 'bot.desk.session', 'bot.device.connect', 'bot.job.dispatch', 'bot.resource.read', 'bot.resource.control'];++// bot.machine.manage: the machines hub (0.258.0, plan L34): Network fronts Bot for a person and for the apps that++// person consented to; Bot stays the authority and checks the machine is that person's own.++const BOT_CAPABILITIES = ['bot.robot.read', 'bot.robot.manage', 'bot.robot.control', 'bot.computer.control', 'bot.machine.ai', 'bot.machine.manage', 'bot.desk.session', 'bot.device.connect', 'bot.job.dispatch', 'bot.resource.read', 'bot.resource.control'];const BOT_EVENTS = ['bot.robot.online', 'bot.robot.offline', 'bot.estop.set', 'bot.estop.cleared', 'bot.command.refused'];const bot = c.services.get('bot');nnew file mode 100644@@@ -0,0 +1,93 @@++'use strict';++// The machines hub (plan L34, steps M2 and M4): a person's machines are the network's, Bot stays the registry++// underneath, and a machine's roles come from a catalogue a product declares in its own manifest. The schemas++// carry the shapes; the rules they cannot say are checked here: one catalogue, one id shape everywhere, and a++// role a product declared is always a role the hub accepts.++const assert = require('assert');++const fs = require('fs');++const path = require('path');++const c = require('..');++++const ROOT = path.join(__dirname, '..');++const schema = (f) => JSON.parse(fs.readFileSync(path.join(ROOT, 'contracts', f), 'utf8'));++const manifest = (id) => JSON.parse(fs.readFileSync(path.join(ROOT, 'manifests', 'services', `${id}.json`), 'utf8'));++const cap = (id) => JSON.parse(fs.readFileSync(path.join(ROOT, 'manifests', 'capabilities', `${id}.json`), 'utf8'));++++const ROLES = {++ 'codes.worker': 'codes',++ 'desks.host': 'run',++ 'ai.host': 'ai',++ 'bot.robot': 'bot',++};++++// One catalogue: every product that declares a role declares it once, and nobody declares another's.++const declared = new Map();++for (const m of c.services.manifests) {++ for (const role of m.machineRoles || []) {++ assert.ok(!declared.has(role.id), `${role.id} is declared once (${m.id} and ${declared.get(role.id)})`);++ declared.set(role.id, m.id);++ assert.ok(role.name && role.installs && role.shows, `${role.id} says what it is, what it installs and what it shows`);++ }++}++assert.deepStrictEqual(Object.keys(ROLES).sort(), [...declared.keys()].sort(), 'the catalogue holds the four first roles');++for (const [id, owner] of Object.entries(ROLES)) assert.strictEqual(declared.get(id), owner, `${id} is ${owner}'s role`);++++// One id shape everywhere: a machine, the catalogue and a request all name a role the same way.++const machine = schema('network/machine.v1.json');++const role = schema('network/machine-role.v1.json');++const roles = schema('network/machine-roles.v1.json');++const put = schema('network/machine-roles-put.v1.json');++const pattern = role.properties.id.pattern;++assert.strictEqual(machine.properties.roles.items.pattern, pattern, 'a machine names its roles the same way');++assert.strictEqual(put.properties.roles.items.pattern, pattern, 'a request names its roles the same way');++for (const id of Object.keys(ROLES)) {++ assert.ok(new RegExp(pattern).test(id), `${id} is a role id`);++ assert.ok(c.validate('network.machine-role@1', { id, name: 'x', installs: 'y', shows: 'z' }).valid, `${id} is a role`);++}++++// The catalogue answers with the roles, and only with them.++const catalogue = { roles: [...declared.keys()].map((id) => ({ id, name: 'x', installs: 'y', shows: 'z' })) };++assert.ok(c.validate('network.machine-roles@1', catalogue).valid, 'the catalogue answers with its roles');++assert.ok(!c.validate('network.machine-roles@1', { roles: [] }).valid, 'an empty catalogue is no catalogue');++assert.ok(!c.validate('network.machine-roles-put@1', { roles: ['not-a-role'] }).valid, 'a request names roles that exist as ids');++++// A machine is a machine: an id, a kind, whether it is online, its roles and what it does now.++for (const k of ['id', 'kind', 'online', 'roles', 'running']) assert.ok(machine.required.includes(k), `network.machine@1 needs ${k}`);++assert.deepStrictEqual(machine.properties.running.items.properties.kind.enum, ['codes.jobs', 'desks', 'models', 'robots'],++ 'a machine says what it does now: its jobs, its desks, its models and its robots');++assert.strictEqual(machine.additionalProperties, false, 'a machine carries nothing else');++++// A pairing is a code, an expiry and the command to run; the code is shown once.++for (const k of ['code', 'expires_at', 'installer']) assert.ok(schema('network/machine-pairing.v1.json').required.includes(k), `the pairing needs ${k}`);++++// The capabilities: the hub's two are public (an app may hold them with the person's consent), Bot's is not.++assert.strictEqual(cap('network.machines.read').visibility, 'public');++assert.strictEqual(cap('network.machines.read').owner, 'network');++assert.strictEqual(cap('network.machines.manage').visibility, 'public');++assert.strictEqual(cap('network.machines.manage').owner, 'network');++assert.strictEqual(cap('network.machines.manage').sensitive, true, 'revoking a machine is an act with an effect outside');++assert.strictEqual(cap('bot.machine.manage').visibility, 'first-party');++assert.strictEqual(cap('bot.machine.manage').owner, 'bot');++++// The manifests say who serves what.++const network = manifest('network');++assert.ok(network.capabilities.includes('network.machines.read') && network.capabilities.includes('network.machines.manage'));++for (const e of ['network.machine.online', 'network.machine.offline', 'network.machine.paired', 'network.machine.roles_changed', 'network.machine.revoked']) {++ assert.ok(network.eventsProduced.includes(e), `network produces ${e}`);++}++assert.ok(manifest('bot').capabilities.includes('bot.machine.manage'), 'bot serves bot.machine.manage');++++// The fixtures: each valid one validates, each invalid one does not.++for (const id of ['network.machine', 'network.machine-role', 'network.machine-roles', 'network.machine-roles-put', 'network.machine-pairing',++ 'network.machine-list-result', 'network.machine-manage-result', 'bot.machine-manage-request', 'bot.machine-manage-result',++ 'network.machine.online', 'network.machine.offline', 'network.machine.paired', 'network.machine.roles_changed', 'network.machine.revoked']) {++ for (const kind of ['valid', 'invalid']) {++ for (const f of fs.readdirSync(path.join(ROOT, 'fixtures', id, kind))) {++ const value = JSON.parse(fs.readFileSync(path.join(ROOT, 'fixtures', id, kind, f), 'utf8'));++ const r = c.validate(`${id}@1`, value);++ assert.strictEqual(r.valid, kind === 'valid', `${id} ${kind}/${f}: ${JSON.stringify(r.errors)}`);++ }++ }++}++++console.log('network.machine@1: one catalogue, one role id shape, four roles, public hub capabilities, fixtures');@@@ -1,10 +1,16 @@'use strict';// ADR-005 amendment 1: people's presence is ephemeral, in Chat's delivery plane.// ADR-043 records robot connectivity in Events; it is not a person's presence.++// The machines hub (plan L34) records the same for a person's machines: a machine coming online is the++// device changing state, never the person being present. Any other `.online`, `.offline` or presence event++// type is still refused.const assert = require('assert');const c = require('..');const produced = c.services.manifests.flatMap((m) => m.eventsProduced || []);--const robotConnectivity = new Set(['bot.robot.online', 'bot.robot.offline']);--const presenceEvents = produced.filter((t) => /presence|\.online|\.offline/i.test(t) && !robotConnectivity.has(t));++const deviceConnectivity = new Set([++ 'bot.robot.online', 'bot.robot.offline',++ 'network.machine.online', 'network.machine.offline',++]);++const presenceEvents = produced.filter((t) => /presence|\.online|\.offline/i.test(t) && !deviceConnectivity.has(t));assert.ok(presenceEvents.length === 0, `no person presence event type: ${presenceEvents}`);console.log('presence policy: all checks passed');@@@ -929,6 +929,8 @@ sub(path.join(__dirname, 'resources.test.js'));// The wait record (plan A2): a wait is a record, not a running loop.sub(path.join(__dirname, 'actor-wait.test.js'));sub(path.join(__dirname, 'network-consent.test.js'));++// The machines hub (plan L34): one catalogue of roles, declared by the products that own them.++sub(path.join(__dirname, 'machines.test.js'));// Labels and choices (L1): one label list, one choice list, and the AI's suggestion stored as suggested.sub(path.join(__dirname, 'content-labels.test.js'));// Documentation currency (roadmap WS-U task 3): a placeholder service with no repository yet is skipped, not a gap.
@@ -1,5 +1,34 @@
@@ -1,5 +1,34 @@
# Changelog
# Changelog
+## 0.261.0 — 2026-10-31
+
+- **The machines hub** (plan L34, steps M2 and M4). A person's computers are the network's: Bot stays the device
+ registry underneath, and Network fronts it on openvibe.network/machines for the person and for apps the person
+ consented to.
+ - **`network.machine@1`:** one machine as the hub shows it — its id (Bot's `dev_…`), name, kind, os, whether it is
+ online, when it was last seen, its `roles` and what it does right now (`running`: its Codes jobs, its desks, its
+ local models, its robots).
+ - **`network.machine-roles@1` and `network.machine-role@1`:** the catalogue of roles. Each role has a name, what it
+ installs on the machine and what the hub shows. A product declares its roles in its own service manifest
+ (`machineRoles`), so the catalogue is the union of what the products declare: `codes.worker` (Codes), `desks.host`
+ (Run), `ai.host` (AI) and `bot.robot` (Bot) are the first four.
+ - **`network.machine-roles-put@1`:** the roles a machine has after a change — the whole set, not a diff.
+ - **`network.machine-pairing@1`:** a one-time pairing code, its expiry and the command to run on the machine.
+ - **`network.machine-list-result@1` and `network.machine-manage-result@1`:** what the hub answers with.
+ - **`bot.machine-manage-request@1` and `bot.machine-manage-result@1`:** what Bot's machines API takes and answers
+ with. `bot.device@1` gains `roles` and `os` (both optional; the old shape still holds).
+ - **The capabilities:** `network.machines.read` and `network.machines.manage` (owner `network`, public: an app may
+ hold them with the person's consent, and may then do only what the person could) and `bot.machine.manage` (owner
+ `bot`, first-party: Network holds it for the hub, so Bot stays the authority and checks the machine is the named
+ person's own).
+ - **The events:** `network.machine.online`, `network.machine.offline`, `network.machine.paired`,
+ `network.machine.roles_changed` and `network.machine.revoked` — Network publishes them through OpenVibe.Events.
+ - **Presence policy:** `network.machine.online` and `network.machine.offline` are a machine's connectivity (the
+ machines hub, plan L34), not a person's presence, and are allowed like `bot.robot.online` and `bot.robot.offline`
+ (ADR-043). Any other `.online`, `.offline` or presence event type is still refused.
+ - **`registry.service-manifest@1` gains `machineRoles`** (optional): the roles a product declares. The manifest
+ keeps its old shape; a manifest without the field means a product with no machine role.
+
## 0.260.0 — 2026-10-31
## 0.260.0 — 2026-10-31
- **One AI meter across the network** (plan L40): every AI call on the network leaves one usage record, whoever made it.
- **One AI meter across the network** (plan L40): every AI call on the network leaves one usage record, whoever made it.
@@ -68,6 +68,21 @@
@@ -68,6 +68,21 @@
},
},
"online": {
"online": {
"type": "boolean"
"type": "boolean"
+ },
+ "roles": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"
+ },
+ "description": "The machine roles the owner set (plan L34: the catalogue is network.machine-roles@1). Absent on a device that has none."
+ },
+ "os": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "The operating system the device reported."
}
}
}
}
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/bot/machine-manage-request.v1.json",
+ "title": "BotMachineManageRequest",
+ "description": "bot.machine-manage-request@1: what a caller changes on a machine (plan L34, step M2). A rename gives a name; a roles change gives the whole set of roles (network.machine-roles-put@1's shape). The same body is what the network's hub sends to Bot, so both sides speak one shape.",
+ "type": "object",
+ "additionalProperties": false,
+ "anyOf": [
+ { "required": ["name"] },
+ { "required": ["roles"] }
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80,
+ "description": "The name the owner gives the machine. Absent when the call changes something else."
+ },
+ "roles": {
+ "type": "array",
+ "uniqueItems": true,
+ "maxItems": 8,
+ "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$" },
+ "description": "The machine's roles after the change. Absent when the call changes something else."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/bot/machine-manage-result.v1.json",
+ "title": "BotMachineManageResult",
+ "description": "bot.machine-manage-result@1: the machine as it is after the change (plan L34, step M2). Bot is the registry underneath: it answers with its own device record, never with a credential.",
+ "type": "object",
+ "required": ["machine"],
+ "additionalProperties": false,
+ "properties": {
+ "machine": { "$ref": "device.v1.json" }
+ }
+}
@@ -5887,6 +5887,132 @@
@@ -5887,6 +5887,132 @@
"status": "active",
"status": "active",
"adr": "ADR-034"
"adr": "ADR-034"
},
},
+ {
+ "id": "network.machine",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/machine.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.machine-list-result",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/machine-list-result.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.machine-manage-result",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/machine-manage-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "bot.machine-manage-request",
+ "version": "1.0.0",
+ "owner": "bot",
+ "schema": "bot/machine-manage-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "bot.machine-manage-result",
+ "version": "1.0.0",
+ "owner": "bot",
+ "schema": "bot/machine-manage-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.machine-role",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/machine-role.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.machine-roles",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/machine-roles.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.machine-roles-put",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/machine-roles-put.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.machine-pairing",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/machine-pairing.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.machine.online",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "events/payloads/network.machine.online.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.machine.offline",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "events/payloads/network.machine.offline.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.machine.paired",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "events/payloads/network.machine.paired.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.machine.roles_changed",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "events/payloads/network.machine.roles_changed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "network.machine.revoked",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "events/payloads/network.machine.revoked.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
{
{
"id": "network.avatar-write-request",
"id": "network.avatar-write-request",
"version": "1.0.0",
"version": "1.0.0",
new file mode 100644
new file mode 100644
@@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.offline.v1.json",
+ "title": "NetworkMachineOfflinePayload",
+ "description": "network.machine.offline v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person went offline: it is no longer connected to Bot. Network republishes Bot's change, so a consumer of the network's events hears about a machine once, in one place. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",
+ "type": "object",
+ "required": ["machine_id", "owner", "at"],
+ "additionalProperties": false,
+ "properties": {
+ "machine_id": { "type": "string", "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "principal_id": { "type": "string", "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The machine's node principal on Network, when it has one." },
+ "owner": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The person the machine belongs to." },
+ "at": { "type": "string", "format": "date-time" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.online.v1.json",
+ "title": "NetworkMachineOnlinePayload",
+ "description": "network.machine.online v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person (a Bot device of kind machine) came online: it checked in and is connected to Bot. Network republishes Bot's change (Bot reports it to Network, which writes this event), so a consumer of the network's events hears about a machine once, in one place. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",
+ "type": "object",
+ "required": ["machine_id", "owner", "at"],
+ "additionalProperties": false,
+ "properties": {
+ "machine_id": { "type": "string", "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "principal_id": { "type": "string", "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The machine's node principal on Network, when it has one." },
+ "owner": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The person the machine belongs to." },
+ "at": { "type": "string", "format": "date-time" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.paired.v1.json",
+ "title": "NetworkMachinePairedPayload",
+ "description": "network.machine.paired v1 (OpenVibe.Network, plan L34: the machines hub). A machine was paired with a person's account: it redeemed the one-time code and became theirs. It holds a credential from then on; the code is spent. The machine's device id in Bot is absent until the machine checks in for the first time. Envelope: subject { type: machine, id: <nod_…> }, visibility internal, actor service:network.",
+ "type": "object",
+ "required": ["principal_id", "owner", "at"],
+ "additionalProperties": false,
+ "properties": {
+ "machine_id": { "type": "string", "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The machine's device id in Bot's registry, when Bot already knows the device." },
+ "node_id": { "type": "string", "pattern": "^n-[a-z0-9]{26}$", "description": "The machine's node id (platform_node_principals.node_id)." },
+ "principal_id": { "type": "string", "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The node principal the pairing created. Also the envelope subject id." },
+ "owner": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The person the machine belongs to." },
+ "at": { "type": "string", "format": "date-time" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.revoked.v1.json",
+ "title": "NetworkMachineRevokedPayload",
+ "description": "network.machine.revoked v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person was revoked: it is no longer theirs and its credential is dead. The machine's credential and its hash are never carried. A second revoke changes nothing and emits nothing. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",
+ "type": "object",
+ "required": ["machine_id", "owner", "at"],
+ "additionalProperties": false,
+ "properties": {
+ "machine_id": { "type": "string", "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "principal_id": { "type": "string", "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The machine's node principal on Network, when it had one." },
+ "owner": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The person the machine belonged to." },
+ "reason": { "type": "string", "minLength": 1, "maxLength": 500, "description": "Why it was revoked, when the caller gave one. Absent otherwise." },
+ "at": { "type": "string", "format": "date-time" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.roles_changed.v1.json",
+ "title": "NetworkMachineRolesChangedPayload",
+ "description": "network.machine.roles_changed v1 (OpenVibe.Network, plan L34, step M4). A machine's roles changed: the whole set after the change, from the catalogue (network.machine-roles@1). The machine applies them at its next check-in, so this event says what was asked for, not what the machine has already done. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",
+ "type": "object",
+ "required": ["machine_id", "owner", "roles", "at"],
+ "additionalProperties": false,
+ "properties": {
+ "machine_id": { "type": "string", "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "owner": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The person the machine belongs to." },
+ "roles": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$" },
+ "description": "The machine's roles after the change."
+ },
+ "changed_by": {
+ "type": "string",
+ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|app:[a-z0-9-]+)$",
+ "description": "Who asked for the change: the person, or an app acting with their consent."
+ },
+ "at": { "type": "string", "format": "date-time" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine-list-result.v1.json",
+ "title": "NetworkMachineListResult",
+ "description": "network.machine-list-result@1: a person's machines, as the network's hub lists them (plan L34, step M2: GET /api/v1/machines). Only the caller's own machines are ever in it.",
+ "type": "object",
+ "required": ["machines"],
+ "additionalProperties": false,
+ "properties": {
+ "machines": {
+ "type": "array",
+ "items": { "$ref": "machine.v1.json" }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine-manage-result.v1.json",
+ "title": "NetworkMachineManageResult",
+ "description": "network.machine-manage-result@1: the machine as it is after a change (plan L34, step M2: PATCH /api/v1/machines/:id, PUT /api/v1/machines/:id/roles, POST /api/v1/machines/:id/revoke). The pairing route answers network.machine-pairing@1 instead, because a pairing is a code and not a machine.",
+ "type": "object",
+ "required": ["machine"],
+ "additionalProperties": false,
+ "properties": {
+ "machine": { "$ref": "machine.v1.json" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine-pairing.v1.json",
+ "title": "NetworkMachinePairing",
+ "description": "network.machine-pairing@1: a one-time code that pairs a machine with the person's account (POST /api/v1/machines/pairings, capability machines.manage). The code is shown once, expires, and works once: the machine redeems it and becomes the person's. The command is what the owner runs on the machine (openvibe.bot/install). The code is never stored in the clear and never returned again.",
+ "type": "object",
+ "required": ["code", "expires_at", "installer"],
+ "additionalProperties": false,
+ "properties": {
+ "code": {
+ "type": "string",
+ "pattern": "^[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}$",
+ "description": "The one-time pairing code, eight Crockford base32 characters in two groups (XXXX-XXXX)."
+ },
+ "pairing_id": {
+ "type": "string",
+ "pattern": "^pair_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The pairing's id, as the install command carries it. Absent when the authority behind it does not name one."
+ },
+ "expires_at": { "type": "string", "format": "date-time", "description": "When the code stops working." },
+ "installer": {
+ "type": "string",
+ "maxLength": 500,
+ "description": "The command the owner runs on the machine (openvibe.bot/install, with the network, the pairing id and the code)."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine-role.v1.json",
+ "title": "NetworkMachineRole",
+ "description": "network.machine-role@1: one role a machine can have (plan L34, step M4). A role is what the machine does for the network: a name for the owner, what it installs on the machine and what the hub shows. The catalogue is network.machine-roles@1; a role outside it is refused.",
+ "type": "object",
+ "required": ["id", "name", "installs", "shows"],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$",
+ "description": "The role's id: the product that owns it, then what it is (codes.worker, desks.host, ai.host, bot.robot)."
+ },
+ "name": { "type": "string", "maxLength": 60, "description": "The role's name, in the owner's words." },
+ "installs": { "type": "string", "maxLength": 200, "description": "What the role puts on the machine (the job runner, the desk sandbox, the local model server)." },
+ "shows": { "type": "string", "maxLength": 200, "description": "What the hub shows for a machine with this role." }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine-roles-put.v1.json",
+ "title": "NetworkMachineRolesPut",
+ "description": "network.machine-roles-put@1: the roles the owner gives a machine (PUT /api/v1/machines/:id/roles, capability machines.manage). The whole set, not a change: what is not named is turned off. Every id must be in the catalogue (network.machine-roles@1); an unknown role is refused and nothing is changed.",
+ "type": "object",
+ "required": ["roles"],
+ "additionalProperties": false,
+ "properties": {
+ "roles": {
+ "type": "array",
+ "uniqueItems": true,
+ "maxItems": 8,
+ "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$" },
+ "description": "The machine's roles after the change. An empty array turns every role off."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine-roles.v1.json",
+ "title": "NetworkMachineRoles",
+ "description": "network.machine-roles@1: the catalogue of machine roles (plan L34, step M4). A product declares a role — a name, what it installs on the machine and what the hub shows — and the Node turns it on at its next check-in. The first roles are Codes' worker, Desks' host, AI's local models and Bot's robot. A role outside this catalogue is refused (network.machine-roles-put@1).",
+ "type": "object",
+ "required": ["roles"],
+ "additionalProperties": false,
+ "properties": {
+ "roles": {
+ "type": "array",
+ "minItems": 1,
+ "uniqueItems": true,
+ "items": { "$ref": "machine-role.v1.json" }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine.v1.json",
+ "title": "NetworkMachine",
+ "description": "network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Node install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.",
+ "type": "object",
+ "required": ["id", "kind", "online", "roles", "running"],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine's device id in Bot's registry (dev_…)."
+ },
+ "principal_id": {
+ "type": "string",
+ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine's node principal on Network (nod_…): the credential it holds. Absent for a machine Bot knows but Network has no principal for."
+ },
+ "name": { "type": "string", "maxLength": 80, "description": "The name the owner gave it. Absent when they gave none." },
+ "kind": {
+ "enum": ["onboard", "bridge", "server"],
+ "description": "What the machine is, as Bot's device record says: onboard (the robot's own machine), bridge (a machine that drives a robot over the robot's own link), server (a machine that runs its owner's jobs)."
+ },
+ "os": { "type": "string", "maxLength": 40, "description": "The operating system the Node reports (linux, darwin, windows). Absent until the machine has checked in." },
+ "online": { "type": "boolean", "description": "Whether the machine is connected to Bot right now." },
+ "last_seen_at": { "type": "string", "format": "date-time", "description": "When the machine was last seen. Absent before its first check-in." },
+ "node_version": { "type": "string", "maxLength": 40, "description": "The OpenVibe Node version the machine reports. Absent until it has checked in." },
+ "roles": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$" },
+ "description": "What the machine does for the network: the ids from the catalogue (network.machine-roles@1). The owner sets these; the machine applies them at its next check-in."
+ },
+ "running": {
+ "type": "array",
+ "description": "What the machine is doing right now, as a short list: its Codes jobs, its desks, the local models it serves and the robots it drives.",
+ "items": {
+ "type": "object",
+ "required": ["kind", "count"],
+ "additionalProperties": false,
+ "properties": {
+ "kind": { "enum": ["codes.jobs", "desks", "models", "robots"] },
+ "count": { "type": "integer", "minimum": 0 },
+ "label": { "type": "string", "maxLength": 120, "description": "A short, plain description for the owner (the desk's name, the model's name)." }
+ }
+ }
+ }
+ }
+}
@@ -218,6 +218,37 @@
@@ -218,6 +218,37 @@
"type": "string"
"type": "string"
}
}
},
},
+ "machineRoles": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "installs",
+ "shows"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 60
+ },
+ "installs": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "shows": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ }
+ },
"contractRanges": {
"contractRanges": {
"type": "object",
"type": "object",
"additionalProperties": {
"additionalProperties": {
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "roles": [
+ "codesworker"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "nickname": "x"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "name": "Kitchen server"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "roles": [
+ "codes.worker",
+ "ai.host"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "machine": {
+ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "robot_ids": [],
+ "kind": "server",
+ "name": "Kitchen server",
+ "agent_version": "0.15.0",
+ "drivers": [],
+ "capabilities": {},
+ "last_seen": "2026-10-31T09:00:00.000Z",
+ "revoked_at": null,
+ "created_at": "2026-10-31T09:00:00.000Z",
+ "updated_at": "2026-10-31T09:00:00.000Z",
+ "online": true,
+ "roles": [
+ "codes.worker"
+ ],
+ "os": "linux"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "machines": {
+ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
+{
+ "machines": [
+ {
+ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "name": "Kitchen server",
+ "kind": "server",
+ "os": "linux",
+ "online": true,
+ "last_seen_at": "2026-10-31T09:00:00.000Z",
+ "node_version": "0.15.0",
+ "roles": [
+ "codes.worker"
+ ],
+ "running": [
+ {
+ "kind": "codes.jobs",
+ "count": 1
+ }
+ ]
+ },
+ {
+ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "kind": "onboard",
+ "online": false,
+ "roles": [],
+ "running": []
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
+{
+ "machine": {
+ "id": "x",
+ "kind": "server",
+ "online": false,
+ "roles": [],
+ "running": []
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "machine": {
+ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "name": "Kitchen server",
+ "kind": "server",
+ "os": "linux",
+ "online": true,
+ "last_seen_at": "2026-10-31T09:00:00.000Z",
+ "node_version": "0.15.0",
+ "roles": [
+ "codes.worker"
+ ],
+ "running": [
+ {
+ "kind": "codes.jobs",
+ "count": 1
+ }
+ ]
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "code": "abcd1234",
+ "expires_at": "2026-10-31T09:00:00.000Z",
+ "installer": "x"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "code": "ABCD-1234",
+ "expires_at": "2026-10-31T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "code": "ABCD-1234",
+ "pairing_id": "pair_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "expires_at": "2026-10-31T09:00:00.000Z",
+ "installer": "curl -fsSL https://openvibe.bot/install | sh -s -- --network https://openvibe.network --pairing pair_01JAB2C3D4E5F6G7H8J9K0MNPS --code ABCD-1234"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "id": "codes.worker",
+ "name": "Codes worker",
+ "installs": "the Codes job runner"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "id": "worker",
+ "name": "Codes worker",
+ "installs": "x",
+ "shows": "y"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "id": "codes.worker",
+ "name": "Codes worker",
+ "installs": "the Codes job runner",
+ "shows": "Runs your coding jobs"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "roles": [
+ "codes.worker",
+ "codes.worker"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "roles": "codes.worker"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "roles": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "roles": [
+ "codes.worker",
+ "ai.host"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "roles": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "roles": "codes.worker"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
+{
+ "roles": [
+ {
+ "id": "codes.worker",
+ "name": "Codes worker",
+ "installs": "Codes worker on the machine",
+ "shows": "Runs codes worker for you"
+ },
+ {
+ "id": "desks.host",
+ "name": "Desk host",
+ "installs": "Desk host on the machine",
+ "shows": "Runs desk host for you"
+ },
+ {
+ "id": "ai.host",
+ "name": "AI host",
+ "installs": "AI host on the machine",
+ "shows": "Runs ai host for you"
+ },
+ {
+ "id": "bot.robot",
+ "name": "Robot",
+ "installs": "Robot on the machine",
+ "shows": "Runs robot for you"
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "owner": "usr_x",
+ "at": "2026-10-31T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "at": "2026-10-31T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "machine_id": "dev_x",
+ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "at": "2026-10-31T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "at": "2026-10-31T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "at": "2026-10-31T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "node_id": "n-01jab2c3d4e5f6g7h8j9k0mnps",
+ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "at": "2026-10-31T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "at": "2026-10-31T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "reason": "the owner removed it",
+ "at": "2026-10-31T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "roles": [
+ "nope"
+ ],
+ "at": "2026-10-31T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "machine_id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "owner": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "roles": [
+ "codes.worker",
+ "ai.host"
+ ],
+ "changed_by": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "at": "2026-10-31T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "id": "machine-1",
+ "kind": "server",
+ "online": false,
+ "roles": [],
+ "running": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
+{
+ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "kind": "server",
+ "online": false,
+ "roles": [
+ "codesworker"
+ ],
+ "running": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "kind": "server",
+ "online": false,
+ "roles": [],
+ "running": [],
+ "last_seen_at": "yesterday"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "kind": "server",
+ "online": false,
+ "roles": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "kind": "laptop",
+ "online": false,
+ "roles": [],
+ "running": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
+{
+ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "kind": "onboard",
+ "online": false,
+ "roles": [
+ "bot.robot"
+ ],
+ "running": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "id": "dev_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "principal_id": "nod_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "name": "Kitchen server",
+ "kind": "server",
+ "os": "linux",
+ "online": true,
+ "last_seen_at": "2026-10-31T09:00:00.000Z",
+ "node_version": "0.15.0",
+ "roles": [
+ "codes.worker"
+ ],
+ "running": [
+ {
+ "kind": "codes.jobs",
+ "count": 2
+ },
+ {
+ "kind": "models",
+ "count": 1,
+ "label": "llama3"
+ }
+ ]
+}
@@ -939,6 +939,37 @@
@@ -939,6 +939,37 @@
"type": "string"
"type": "string"
}
}
},
},
+ "machineRoles": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "installs",
+ "shows"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 60
+ },
+ "installs": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "shows": {
+ "type": "string",
+ "maxLength": 200
+ }
+ }
+ }
+ },
"contractRanges": {
"contractRanges": {
"type": "object",
"type": "object",
"additionalProperties": {
"additionalProperties": {
@@ -67245,6 +67276,566 @@
@@ -67245,6 +67276,566 @@
}
}
}
}
},
},
+ "network.machine": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine.v1.json",
+ "title": "NetworkMachine",
+ "description": "network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Node install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.",
+ "type": "object",
+ "required": [
+ "id",
+ "kind",
+ "online",
+ "roles",
+ "running"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine's device id in Bot's registry (dev_…)."
+ },
+ "principal_id": {
+ "type": "string",
+ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine's node principal on Network (nod_…): the credential it holds. Absent for a machine Bot knows but Network has no principal for."
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 80,
+ "description": "The name the owner gave it. Absent when they gave none."
+ },
+ "kind": {
+ "enum": [
+ "onboard",
+ "bridge",
+ "server"
+ ],
+ "description": "What the machine is, as Bot's device record says: onboard (the robot's own machine), bridge (a machine that drives a robot over the robot's own link), server (a machine that runs its owner's jobs)."
+ },
+ "os": {
+ "type": "string",
+ "maxLength": 40,
+ "description": "The operating system the Node reports (linux, darwin, windows). Absent until the machine has checked in."
+ },
+ "online": {
+ "type": "boolean",
+ "description": "Whether the machine is connected to Bot right now."
+ },
+ "last_seen_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the machine was last seen. Absent before its first check-in."
+ },
+ "node_version": {
+ "type": "string",
+ "maxLength": 40,
+ "description": "The OpenVibe Node version the machine reports. Absent until it has checked in."
+ },
+ "roles": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"
+ },
+ "description": "What the machine does for the network: the ids from the catalogue (network.machine-roles@1). The owner sets these; the machine applies them at its next check-in."
+ },
+ "running": {
+ "type": "array",
+ "description": "What the machine is doing right now, as a short list: its Codes jobs, its desks, the local models it serves and the robots it drives.",
+ "items": {
+ "type": "object",
+ "required": [
+ "kind",
+ "count"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "kind": {
+ "enum": [
+ "codes.jobs",
+ "desks",
+ "models",
+ "robots"
+ ]
+ },
+ "count": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "label": {
+ "type": "string",
+ "maxLength": 120,
+ "description": "A short, plain description for the owner (the desk's name, the model's name)."
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "network.machine-list-result": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine-list-result.v1.json",
+ "title": "NetworkMachineListResult",
+ "description": "network.machine-list-result@1: a person's machines, as the network's hub lists them (plan L34, step M2: GET /api/v1/machines). Only the caller's own machines are ever in it.",
+ "type": "object",
+ "required": [
+ "machines"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "machines": {
+ "type": "array",
+ "items": {
+ "$ref": "machine.v1.json"
+ }
+ }
+ }
+ }
+ },
+ "network.machine-manage-result": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine-manage-result.v1.json",
+ "title": "NetworkMachineManageResult",
+ "description": "network.machine-manage-result@1: the machine as it is after a change (plan L34, step M2: PATCH /api/v1/machines/:id, PUT /api/v1/machines/:id/roles, POST /api/v1/machines/:id/revoke). The pairing route answers network.machine-pairing@1 instead, because a pairing is a code and not a machine.",
+ "type": "object",
+ "required": [
+ "machine"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "machine": {
+ "$ref": "machine.v1.json"
+ }
+ }
+ }
+ },
+ "bot.machine-manage-request": {
+ "version": "1.0.0",
+ "owner": "bot",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/bot/machine-manage-request.v1.json",
+ "title": "BotMachineManageRequest",
+ "description": "bot.machine-manage-request@1: what a caller changes on a machine (plan L34, step M2). A rename gives a name; a roles change gives the whole set of roles (network.machine-roles-put@1's shape). The same body is what the network's hub sends to Bot, so both sides speak one shape.",
+ "type": "object",
+ "additionalProperties": false,
+ "anyOf": [
+ {
+ "required": [
+ "name"
+ ]
+ },
+ {
+ "required": [
+ "roles"
+ ]
+ }
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80,
+ "description": "The name the owner gives the machine. Absent when the call changes something else."
+ },
+ "roles": {
+ "type": "array",
+ "uniqueItems": true,
+ "maxItems": 8,
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"
+ },
+ "description": "The machine's roles after the change. Absent when the call changes something else."
+ }
+ }
+ }
+ },
+ "bot.machine-manage-result": {
+ "version": "1.0.0",
+ "owner": "bot",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/bot/machine-manage-result.v1.json",
+ "title": "BotMachineManageResult",
+ "description": "bot.machine-manage-result@1: the machine as it is after the change (plan L34, step M2). Bot is the registry underneath: it answers with its own device record, never with a credential.",
+ "type": "object",
+ "required": [
+ "machine"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "machine": {
+ "$ref": "device.v1.json"
+ }
+ }
+ }
+ },
+ "network.machine-role": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine-role.v1.json",
+ "title": "NetworkMachineRole",
+ "description": "network.machine-role@1: one role a machine can have (plan L34, step M4). A role is what the machine does for the network: a name for the owner, what it installs on the machine and what the hub shows. The catalogue is network.machine-roles@1; a role outside it is refused.",
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "installs",
+ "shows"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$",
+ "description": "The role's id: the product that owns it, then what it is (codes.worker, desks.host, ai.host, bot.robot)."
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 60,
+ "description": "The role's name, in the owner's words."
+ },
+ "installs": {
+ "type": "string",
+ "maxLength": 200,
+ "description": "What the role puts on the machine (the job runner, the desk sandbox, the local model server)."
+ },
+ "shows": {
+ "type": "string",
+ "maxLength": 200,
+ "description": "What the hub shows for a machine with this role."
+ }
+ }
+ }
+ },
+ "network.machine-roles": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine-roles.v1.json",
+ "title": "NetworkMachineRoles",
+ "description": "network.machine-roles@1: the catalogue of machine roles (plan L34, step M4). A product declares a role — a name, what it installs on the machine and what the hub shows — and the Node turns it on at its next check-in. The first roles are Codes' worker, Desks' host, AI's local models and Bot's robot. A role outside this catalogue is refused (network.machine-roles-put@1).",
+ "type": "object",
+ "required": [
+ "roles"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "roles": {
+ "type": "array",
+ "minItems": 1,
+ "uniqueItems": true,
+ "items": {
+ "$ref": "machine-role.v1.json"
+ }
+ }
+ }
+ }
+ },
+ "network.machine-roles-put": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine-roles-put.v1.json",
+ "title": "NetworkMachineRolesPut",
+ "description": "network.machine-roles-put@1: the roles the owner gives a machine (PUT /api/v1/machines/:id/roles, capability machines.manage). The whole set, not a change: what is not named is turned off. Every id must be in the catalogue (network.machine-roles@1); an unknown role is refused and nothing is changed.",
+ "type": "object",
+ "required": [
+ "roles"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "roles": {
+ "type": "array",
+ "uniqueItems": true,
+ "maxItems": 8,
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"
+ },
+ "description": "The machine's roles after the change. An empty array turns every role off."
+ }
+ }
+ }
+ },
+ "network.machine-pairing": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/machine-pairing.v1.json",
+ "title": "NetworkMachinePairing",
+ "description": "network.machine-pairing@1: a one-time code that pairs a machine with the person's account (POST /api/v1/machines/pairings, capability machines.manage). The code is shown once, expires, and works once: the machine redeems it and becomes the person's. The command is what the owner runs on the machine (openvibe.bot/install). The code is never stored in the clear and never returned again.",
+ "type": "object",
+ "required": [
+ "code",
+ "expires_at",
+ "installer"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "code": {
+ "type": "string",
+ "pattern": "^[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}$",
+ "description": "The one-time pairing code, eight Crockford base32 characters in two groups (XXXX-XXXX)."
+ },
+ "pairing_id": {
+ "type": "string",
+ "pattern": "^pair_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The pairing's id, as the install command carries it. Absent when the authority behind it does not name one."
+ },
+ "expires_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the code stops working."
+ },
+ "installer": {
+ "type": "string",
+ "maxLength": 500,
+ "description": "The command the owner runs on the machine (openvibe.bot/install, with the network, the pairing id and the code)."
+ }
+ }
+ }
+ },
+ "network.machine.online": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.online.v1.json",
+ "title": "NetworkMachineOnlinePayload",
+ "description": "network.machine.online v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person (a Bot device of kind machine) came online: it checked in and is connected to Bot. Network republishes Bot's change (Bot reports it to Network, which writes this event), so a consumer of the network's events hears about a machine once, in one place. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",
+ "type": "object",
+ "required": [
+ "machine_id",
+ "owner",
+ "at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "machine_id": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "principal_id": {
+ "type": "string",
+ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine's node principal on Network, when it has one."
+ },
+ "owner": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person the machine belongs to."
+ },
+ "at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "network.machine.offline": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.offline.v1.json",
+ "title": "NetworkMachineOfflinePayload",
+ "description": "network.machine.offline v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person went offline: it is no longer connected to Bot. Network republishes Bot's change, so a consumer of the network's events hears about a machine once, in one place. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",
+ "type": "object",
+ "required": [
+ "machine_id",
+ "owner",
+ "at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "machine_id": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "principal_id": {
+ "type": "string",
+ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine's node principal on Network, when it has one."
+ },
+ "owner": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person the machine belongs to."
+ },
+ "at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "network.machine.paired": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.paired.v1.json",
+ "title": "NetworkMachinePairedPayload",
+ "description": "network.machine.paired v1 (OpenVibe.Network, plan L34: the machines hub). A machine was paired with a person's account: it redeemed the one-time code and became theirs. It holds a credential from then on; the code is spent. The machine's device id in Bot is absent until the machine checks in for the first time. Envelope: subject { type: machine, id: <nod_…> }, visibility internal, actor service:network.",
+ "type": "object",
+ "required": [
+ "principal_id",
+ "owner",
+ "at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "machine_id": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine's device id in Bot's registry, when Bot already knows the device."
+ },
+ "node_id": {
+ "type": "string",
+ "pattern": "^n-[a-z0-9]{26}$",
+ "description": "The machine's node id (platform_node_principals.node_id)."
+ },
+ "principal_id": {
+ "type": "string",
+ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The node principal the pairing created. Also the envelope subject id."
+ },
+ "owner": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person the machine belongs to."
+ },
+ "at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "network.machine.roles_changed": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.roles_changed.v1.json",
+ "title": "NetworkMachineRolesChangedPayload",
+ "description": "network.machine.roles_changed v1 (OpenVibe.Network, plan L34, step M4). A machine's roles changed: the whole set after the change, from the catalogue (network.machine-roles@1). The machine applies them at its next check-in, so this event says what was asked for, not what the machine has already done. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",
+ "type": "object",
+ "required": [
+ "machine_id",
+ "owner",
+ "roles",
+ "at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "machine_id": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "owner": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person the machine belongs to."
+ },
+ "roles": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"
+ },
+ "description": "The machine's roles after the change."
+ },
+ "changed_by": {
+ "type": "string",
+ "pattern": "^(usr_[0-9A-HJKMNP-TV-Z]{26}|app:[a-z0-9-]+)$",
+ "description": "Who asked for the change: the person, or an app acting with their consent."
+ },
+ "at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "network.machine.revoked": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.machine.revoked.v1.json",
+ "title": "NetworkMachineRevokedPayload",
+ "description": "network.machine.revoked v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person was revoked: it is no longer theirs and its credential is dead. The machine's credential and its hash are never carried. A second revoke changes nothing and emits nothing. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.",
+ "type": "object",
+ "required": [
+ "machine_id",
+ "owner",
+ "at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "machine_id": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "principal_id": {
+ "type": "string",
+ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine's node principal on Network, when it had one."
+ },
+ "owner": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person the machine belonged to."
+ },
+ "reason": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 500,
+ "description": "Why it was revoked, when the caller gave one. Absent otherwise."
+ },
+ "at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
"network.avatar-write-request": {
"network.avatar-write-request": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "network",
"owner": "network",
@@ -72552,6 +73143,21 @@
@@ -72552,6 +73143,21 @@
},
},
"online": {
"online": {
"type": "boolean"
"type": "boolean"
+ },
+ "roles": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"
+ },
+ "description": "The machine roles the owner set (plan L34: the catalogue is network.machine-roles@1). Absent on a device that has none."
+ },
+ "os": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "The operating system the device reported."
}
}
}
}
}
}
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Actor API",
"title": "OpenVibe.Actor API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.AI API",
"title": "OpenVibe.AI API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Bench API",
"title": "OpenVibe.Bench API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Billing API",
"title": "OpenVibe.Billing API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Blog API",
"title": "OpenVibe.Blog API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Bot API",
"title": "OpenVibe.Bot API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -472,6 +472,178 @@
@@ -472,6 +472,178 @@
}
}
}
}
},
},
+ "/api/v1/machines": {
+ "get": {
+ "operationId": "get_api_v1_machines",
+ "summary": "A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject).",
+ "description": "**bot.machine.manage** (first-party): A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject). Network holds it for the machines hub (openvibe.network/machines): Bot stays the registry underneath, so Bot checks that the machine is the named person's own. Never grantable to apps.",
+ "tags": [
+ "bot.machine"
+ ],
+ "x-openvibe-capabilities": [
+ "bot.machine.manage"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "bot-machine",
+ "security": [
+ {
+ "openvibeToken": [
+ "bot.machine.manage"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/bot.machine-manage-request.v1"
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.machine-manage-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/machines/{id}": {
+ "get": {
+ "operationId": "get_api_v1_machines_id",
+ "summary": "A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject).",
+ "description": "**bot.machine.manage** (first-party): A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject). Network holds it for the machines hub (openvibe.network/machines): Bot stays the registry underneath, so Bot checks that the machine is the named person's own. Never grantable to apps.",
+ "tags": [
+ "bot.machine"
+ ],
+ "x-openvibe-capabilities": [
+ "bot.machine.manage"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "bot-machine",
+ "security": [
+ {
+ "openvibeToken": [
+ "bot.machine.manage"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/bot.machine-manage-request.v1"
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.machine-manage-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ },
+ "patch": {
+ "operationId": "patch_api_v1_machines_id",
+ "summary": "A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject).",
+ "description": "**bot.machine.manage** (first-party): A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject). Network holds it for the machines hub (openvibe.network/machines): Bot stays the registry underneath, so Bot checks that the machine is the named person's own. Never grantable to apps.",
+ "tags": [
+ "bot.machine"
+ ],
+ "x-openvibe-capabilities": [
+ "bot.machine.manage"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "bot-machine",
+ "security": [
+ {
+ "openvibeToken": [
+ "bot.machine.manage"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.machine-manage-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.machine-manage-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/api/v1/machines/{id}/desk-leases": {
"/api/v1/machines/{id}/desk-leases": {
"post": {
"post": {
"operationId": "post_api_v1_machines_id_desk_leases",
"operationId": "post_api_v1_machines_id_desk_leases",
@@ -816,6 +988,138 @@
@@ -816,6 +988,138 @@
}
}
}
}
},
},
+ "/api/v1/machines/{id}/revoke": {
+ "post": {
+ "operationId": "post_api_v1_machines_id_revoke",
+ "summary": "A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject).",
+ "description": "**bot.machine.manage** (first-party): A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject). Network holds it for the machines hub (openvibe.network/machines): Bot stays the registry underneath, so Bot checks that the machine is the named person's own. Never grantable to apps.",
+ "tags": [
+ "bot.machine"
+ ],
+ "x-openvibe-capabilities": [
+ "bot.machine.manage"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "bot-machine",
+ "security": [
+ {
+ "openvibeToken": [
+ "bot.machine.manage"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.machine-manage-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.machine-manage-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/machines/{id}/roles": {
+ "put": {
+ "operationId": "put_api_v1_machines_id_roles",
+ "summary": "A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject).",
+ "description": "**bot.machine.manage** (first-party): A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject). Network holds it for the machines hub (openvibe.network/machines): Bot stays the registry underneath, so Bot checks that the machine is the named person's own. Never grantable to apps.",
+ "tags": [
+ "bot.machine"
+ ],
+ "x-openvibe-capabilities": [
+ "bot.machine.manage"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "bot-machine",
+ "security": [
+ {
+ "openvibeToken": [
+ "bot.machine.manage"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.machine-manage-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.machine-manage-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/api/v1/resources": {
"/api/v1/resources": {
"get": {
"get": {
"operationId": "get_api_v1_resources",
"operationId": "get_api_v1_resources",
@@ -2092,6 +2396,21 @@
@@ -2092,6 +2396,21 @@
},
},
"online": {
"online": {
"type": "boolean"
"type": "boolean"
+ },
+ "roles": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"
+ },
+ "description": "The machine roles the owner set (plan L34: the catalogue is network.machine-roles@1). Absent on a device that has none."
+ },
+ "os": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "description": "The operating system the device reported."
}
}
}
}
},
},
@@ -2356,6 +2675,56 @@
@@ -2356,6 +2675,56 @@
}
}
}
}
},
},
+ "bot.machine-manage-request.v1": {
+ "title": "BotMachineManageRequest",
+ "description": "bot.machine-manage-request@1: what a caller changes on a machine (plan L34, step M2). A rename gives a name; a roles change gives the whole set of roles (network.machine-roles-put@1's shape). The same body is what the network's hub sends to Bot, so both sides speak one shape.",
+ "type": "object",
+ "additionalProperties": false,
+ "anyOf": [
+ {
+ "required": [
+ "name"
+ ]
+ },
+ {
+ "required": [
+ "roles"
+ ]
+ }
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80,
+ "description": "The name the owner gives the machine. Absent when the call changes something else."
+ },
+ "roles": {
+ "type": "array",
+ "uniqueItems": true,
+ "maxItems": 8,
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"
+ },
+ "description": "The machine's roles after the change. Absent when the call changes something else."
+ }
+ }
+ },
+ "bot.machine-manage-result.v1": {
+ "title": "BotMachineManageResult",
+ "description": "bot.machine-manage-result@1: the machine as it is after the change (plan L34, step M2). Bot is the registry underneath: it answers with its own device record, never with a credential.",
+ "type": "object",
+ "required": [
+ "machine"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "machine": {
+ "$ref": "#/components/schemas/bot.device.v1"
+ }
+ }
+ },
"bot.robot-control-result.v1": {
"bot.robot-control-result.v1": {
"title": "BotRobotControlResult",
"title": "BotRobotControlResult",
"description": "bot.robot-control-result@1: answers of bot.robot.control on OpenVibe.Bot. POST /api/v1/robots/:id/estop → { robot } with its e-stop as it now stands.",
"description": "bot.robot-control-result@1: answers of bot.robot.control on OpenVibe.Bot. POST /api/v1/robots/:id/estop → { robot } with its e-stop as it now stands.",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Chat API",
"title": "OpenVibe.Chat API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Codes API",
"title": "OpenVibe.Codes API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Community API",
"title": "OpenVibe.Community API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Coupons API",
"title": "OpenVibe.Coupons API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Deals API",
"title": "OpenVibe.Deals API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Domains API",
"title": "OpenVibe.Domains API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Events API",
"title": "OpenVibe.Events API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Food API",
"title": "OpenVibe.Food API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Games API",
"title": "OpenVibe.Games API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Host API",
"title": "OpenVibe.Host API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -40,8 +40,8 @@
@@ -40,8 +40,8 @@
"service": "bot",
"service": "bot",
"name": "OpenVibe.Bot",
"name": "OpenVibe.Bot",
"origin": "https://openvibe.bot",
"origin": "https://openvibe.bot",
- "operations": 25,
- "capabilities": 9
+ "operations": 30,
+ "capabilities": 10
},
},
{
{
"service": "chat",
"service": "chat",
@@ -145,8 +145,8 @@
@@ -145,8 +145,8 @@
"service": "network",
"service": "network",
"name": "OpenVibe.Network",
"name": "OpenVibe.Network",
"origin": "https://openvibe.network",
"origin": "https://openvibe.network",
- "operations": 47,
- "capabilities": 32
+ "operations": 54,
+ "capabilities": 34
},
},
{
{
"service": "news",
"service": "news",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Inventory API",
"title": "OpenVibe.Inventory API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Live API",
"title": "OpenVibe.Live API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.MediaHub API",
"title": "OpenVibe.MediaHub API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Media API",
"title": "OpenVibe.Media API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Network API",
"title": "OpenVibe.Network API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -136,6 +136,406 @@
@@ -136,6 +136,406 @@
}
}
}
}
},
},
+ "/api/v1/machines": {
+ "get": {
+ "operationId": "get_api_v1_machines",
+ "summary": "Read a person's own machines: the list, one machine, and the catalogue of roles.",
+ "description": "**network.machines.read** (public): Read a person's own machines: the list, one machine, and the catalogue of roles. A person reads their own with their own token; an app needs the person's consent for this scope (it is a public capability, so it can be granted to an app, and the app then acts only on behalf of the person who consented).",
+ "tags": [
+ "network.machines"
+ ],
+ "x-openvibe-capabilities": [
+ "network.machines.read"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "network-read",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.machines.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.machine-list-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/machines/{id}": {
+ "get": {
+ "operationId": "get_api_v1_machines_id",
+ "summary": "Read a person's own machines: the list, one machine, and the catalogue of roles.",
+ "description": "**network.machines.read** (public): Read a person's own machines: the list, one machine, and the catalogue of roles. A person reads their own with their own token; an app needs the person's consent for this scope (it is a public capability, so it can be granted to an app, and the app then acts only on behalf of the person who consented).",
+ "tags": [
+ "network.machines"
+ ],
+ "x-openvibe-capabilities": [
+ "network.machines.read"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "network-read",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.machines.read"
+ ]
+ }
+ ],
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.machine-list-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ },
+ "patch": {
+ "operationId": "patch_api_v1_machines_id",
+ "summary": "Pair, rename, give roles to and revoke a person's own machines.",
+ "description": "**network.machines.manage** (public): Pair, rename, give roles to and revoke a person's own machines. Bot stays the registry underneath: Network asks Bot as the person, and Bot checks that the machine is theirs. An app needs the person's consent for this scope, and may then do only what the person could.",
+ "tags": [
+ "network.machines"
+ ],
+ "x-openvibe-capabilities": [
+ "network.machines.manage"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "network-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.machines.manage"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.machine-manage-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.machine-manage-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/machines/{id}/revoke": {
+ "post": {
+ "operationId": "post_api_v1_machines_id_revoke",
+ "summary": "Pair, rename, give roles to and revoke a person's own machines.",
+ "description": "**network.machines.manage** (public): Pair, rename, give roles to and revoke a person's own machines. Bot stays the registry underneath: Network asks Bot as the person, and Bot checks that the machine is theirs. An app needs the person's consent for this scope, and may then do only what the person could.",
+ "tags": [
+ "network.machines"
+ ],
+ "x-openvibe-capabilities": [
+ "network.machines.manage"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "network-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.machines.manage"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.machine-manage-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.machine-manage-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/machines/{id}/roles": {
+ "put": {
+ "operationId": "put_api_v1_machines_id_roles",
+ "summary": "Pair, rename, give roles to and revoke a person's own machines.",
+ "description": "**network.machines.manage** (public): Pair, rename, give roles to and revoke a person's own machines. Bot stays the registry underneath: Network asks Bot as the person, and Bot checks that the machine is theirs. An app needs the person's consent for this scope, and may then do only what the person could.",
+ "tags": [
+ "network.machines"
+ ],
+ "x-openvibe-capabilities": [
+ "network.machines.manage"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "network-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.machines.manage"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.machine-manage-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.machine-manage-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/machines/pairings": {
+ "post": {
+ "operationId": "post_api_v1_machines_pairings",
+ "summary": "Pair, rename, give roles to and revoke a person's own machines.",
+ "description": "**network.machines.manage** (public): Pair, rename, give roles to and revoke a person's own machines. Bot stays the registry underneath: Network asks Bot as the person, and Bot checks that the machine is theirs. An app needs the person's consent for this scope, and may then do only what the person could.",
+ "tags": [
+ "network.machines"
+ ],
+ "x-openvibe-capabilities": [
+ "network.machines.manage"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "network-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.machines.manage"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.machine-manage-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.machine-manage-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/machines/roles": {
+ "get": {
+ "operationId": "get_api_v1_machines_roles",
+ "summary": "Read a person's own machines: the list, one machine, and the catalogue of roles.",
+ "description": "**network.machines.read** (public): Read a person's own machines: the list, one machine, and the catalogue of roles. A person reads their own with their own token; an app needs the person's consent for this scope (it is a public capability, so it can be granted to an app, and the app then acts only on behalf of the person who consented).",
+ "tags": [
+ "network.machines"
+ ],
+ "x-openvibe-capabilities": [
+ "network.machines.read"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "network-read",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.machines.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.machine-list-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/api/v1/me/actions": {
"/api/v1/me/actions": {
"get": {
"get": {
"operationId": "get_api_v1_me_actions",
"operationId": "get_api_v1_me_actions",
@@ -2898,6 +3298,42 @@
@@ -2898,6 +3298,42 @@
},
},
"components": {
"components": {
"schemas": {
"schemas": {
+ "bot.machine-manage-request.v1": {
+ "title": "BotMachineManageRequest",
+ "description": "bot.machine-manage-request@1: what a caller changes on a machine (plan L34, step M2). A rename gives a name; a roles change gives the whole set of roles (network.machine-roles-put@1's shape). The same body is what the network's hub sends to Bot, so both sides speak one shape.",
+ "type": "object",
+ "additionalProperties": false,
+ "anyOf": [
+ {
+ "required": [
+ "name"
+ ]
+ },
+ {
+ "required": [
+ "roles"
+ ]
+ }
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80,
+ "description": "The name the owner gives the machine. Absent when the call changes something else."
+ },
+ "roles": {
+ "type": "array",
+ "uniqueItems": true,
+ "maxItems": 8,
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"
+ },
+ "description": "The machine's roles after the change. Absent when the call changes something else."
+ }
+ }
+ },
"common.action-item.v1": {
"common.action-item.v1": {
"title": "ActionItem",
"title": "ActionItem",
"description": "One piece of a person's agent work in the Action Center (plan 2026-10-28 §3.10): an agent task, a coding task, an AI run, or an approval waiting on them. Each authority answers its own items in this shape (common.action-list-result@1); OpenVibe.Network merges them into buckets (network.me-actions-result@1). Text is plain (no markup); a cost is what the person was charged in USD, null when unknown or free; href is a page on the authority's own public site.",
"description": "One piece of a person's agent work in the Action Center (plan 2026-10-28 §3.10): an agent task, a coding task, an AI run, or an approval waiting on them. Each authority answers its own items in this shape (common.action-list-result@1); OpenVibe.Network merges them into buckets (network.me-actions-result@1). Text is plain (no markup); a cost is what the person was charged in USD, null when unknown or free; href is a page on the authority's own public site.",
@@ -4608,6 +5044,134 @@
@@ -4608,6 +5044,134 @@
}
}
}
}
},
},
+ "network.machine-list-result.v1": {
+ "title": "NetworkMachineListResult",
+ "description": "network.machine-list-result@1: a person's machines, as the network's hub lists them (plan L34, step M2: GET /api/v1/machines). Only the caller's own machines are ever in it.",
+ "type": "object",
+ "required": [
+ "machines"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "machines": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/network.machine.v1"
+ }
+ }
+ }
+ },
+ "network.machine-manage-result.v1": {
+ "title": "NetworkMachineManageResult",
+ "description": "network.machine-manage-result@1: the machine as it is after a change (plan L34, step M2: PATCH /api/v1/machines/:id, PUT /api/v1/machines/:id/roles, POST /api/v1/machines/:id/revoke). The pairing route answers network.machine-pairing@1 instead, because a pairing is a code and not a machine.",
+ "type": "object",
+ "required": [
+ "machine"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "machine": {
+ "$ref": "#/components/schemas/network.machine.v1"
+ }
+ }
+ },
+ "network.machine.v1": {
+ "title": "NetworkMachine",
+ "description": "network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Node install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.",
+ "type": "object",
+ "required": [
+ "id",
+ "kind",
+ "online",
+ "roles",
+ "running"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine's device id in Bot's registry (dev_…)."
+ },
+ "principal_id": {
+ "type": "string",
+ "pattern": "^nod_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine's node principal on Network (nod_…): the credential it holds. Absent for a machine Bot knows but Network has no principal for."
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 80,
+ "description": "The name the owner gave it. Absent when they gave none."
+ },
+ "kind": {
+ "enum": [
+ "onboard",
+ "bridge",
+ "server"
+ ],
+ "description": "What the machine is, as Bot's device record says: onboard (the robot's own machine), bridge (a machine that drives a robot over the robot's own link), server (a machine that runs its owner's jobs)."
+ },
+ "os": {
+ "type": "string",
+ "maxLength": 40,
+ "description": "The operating system the Node reports (linux, darwin, windows). Absent until the machine has checked in."
+ },
+ "online": {
+ "type": "boolean",
+ "description": "Whether the machine is connected to Bot right now."
+ },
+ "last_seen_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the machine was last seen. Absent before its first check-in."
+ },
+ "node_version": {
+ "type": "string",
+ "maxLength": 40,
+ "description": "The OpenVibe Node version the machine reports. Absent until it has checked in."
+ },
+ "roles": {
+ "type": "array",
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$"
+ },
+ "description": "What the machine does for the network: the ids from the catalogue (network.machine-roles@1). The owner sets these; the machine applies them at its next check-in."
+ },
+ "running": {
+ "type": "array",
+ "description": "What the machine is doing right now, as a short list: its Codes jobs, its desks, the local models it serves and the robots it drives.",
+ "items": {
+ "type": "object",
+ "required": [
+ "kind",
+ "count"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "kind": {
+ "enum": [
+ "codes.jobs",
+ "desks",
+ "models",
+ "robots"
+ ]
+ },
+ "count": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "label": {
+ "type": "string",
+ "maxLength": 120,
+ "description": "A short, plain description for the owner (the desk's name, the model's name)."
+ }
+ }
+ }
+ }
+ }
+ },
"network.me-actions-result.v1": {
"network.me-actions-result.v1": {
"title": "MeActionsResult",
"title": "MeActionsResult",
"description": "The Action Center for the signed-in person (capability network.actions.read, GET /api/v1/me/actions): every authority's items merged into five buckets, newest first in each. sources says how each authority answered; a slow or failing one is left out (partial true), never fails the answer.",
"description": "The Action Center for the signed-in person (capability network.actions.read, GET /api/v1/me/actions): every authority's items merged into five buckets, newest first in each. sources says how each authority answered; a slow or failing one is left out (partial true), never fails the answer.",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.News API",
"title": "OpenVibe.News API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenRestream API",
"title": "OpenRestream API",
- "version": "0.260.0",
- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Play API",
"title": "OpenVibe.Play API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Quest API",
"title": "OpenVibe.Quest API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Report API",
"title": "OpenVibe.Report API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Reviews API",
"title": "OpenVibe.Reviews API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Run API",
"title": "OpenVibe.Run API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Search API",
"title": "OpenVibe.Search API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Services API",
"title": "OpenVibe.Services API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Sources API",
"title": "OpenVibe.Sources API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Space API",
"title": "OpenVibe.Space API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tips API",
"title": "OpenVibe.Tips API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tools API",
"title": "OpenVibe.Tools API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Trade API",
"title": "OpenVibe.Trade API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.VIP API",
"title": "OpenVibe.VIP API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Vision API",
"title": "OpenVibe.Vision API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Watch API",
"title": "OpenVibe.Watch API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Wiki API",
"title": "OpenVibe.Wiki API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Work API",
"title": "OpenVibe.Work API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Zone API",
"title": "OpenVibe.Zone API",
- "version": "0.260.0",
- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.260.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.261.0",
+ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.261.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -2042,6 +2042,12 @@ export interface ServiceManifest {
@@ -2042,6 +2042,12 @@ export interface ServiceManifest {
eventsProduced: string[];
eventsProduced: string[];
eventsConsumed: string[];
eventsConsumed: string[];
namespacesOwned: string[];
namespacesOwned: string[];
+ machineRoles?: {
+ id: string;
+ name: string;
+ installs: string;
+ shows: string;
+ }[];
contractRanges?: {
contractRanges?: {
[k: string]: string | undefined;
[k: string]: string | undefined;
};
};
@@ -201099,6 +201105,291 @@ export interface NetworkNodeListResult {
@@ -201099,6 +201105,291 @@ export interface NetworkNodeListResult {
generated_at: string;
generated_at: string;
}
}
+/** [email protected] (owner: network) */
+/**
+ * network.machine@1: one machine of a person, as the network's machines hub shows it (plan L34, steps M2 and M4). A machine is a Bot device of kind machine: an OpenVibe Node install that paired with the person's account. Bot stays the registry underneath (the device id is its dev_…); Network fronts it for the person and for apps the person consented. The roles are what the machine does for the network (the catalogue: network.machine-roles@1), and running is what it does right now. Nothing here is a secret: no credential, no hash, no address.
+ */
+export interface NetworkMachine {
+ /**
+ * The machine's device id in Bot's registry (dev_…).
+ */
+ id: string;
+ /**
+ * The machine's node principal on Network (nod_…): the credential it holds. Absent for a machine Bot knows but Network has no principal for.
+ */
+ principal_id?: string;
+ /**
+ * The name the owner gave it. Absent when they gave none.
+ */
+ name?: string;
+ /**
+ * What the machine is, as Bot's device record says: onboard (the robot's own machine), bridge (a machine that drives a robot over the robot's own link), server (a machine that runs its owner's jobs).
+ */
+ kind: "onboard" | "bridge" | "server";
+ /**
+ * The operating system the Node reports (linux, darwin, windows). Absent until the machine has checked in.
+ */
+ os?: string;
+ /**
+ * Whether the machine is connected to Bot right now.
+ */
+ online: boolean;
+ /**
+ * When the machine was last seen. Absent before its first check-in.
+ */
+ last_seen_at?: string;
+ /**
+ * The OpenVibe Node version the machine reports. Absent until it has checked in.
+ */
+ node_version?: string;
+ /**
+ * What the machine does for the network: the ids from the catalogue (network.machine-roles@1). The owner sets these; the machine applies them at its next check-in.
+ */
+ roles: string[];
+ /**
+ * What the machine is doing right now, as a short list: its Codes jobs, its desks, the local models it serves and the robots it drives.
+ */
+ running: {
+ kind: "codes.jobs" | "desks" | "models" | "robots";
+ count: number;
+ /**
+ * A short, plain description for the owner (the desk's name, the model's name).
+ */
+ label?: string;
+ }[];
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.machine-list-result@1: a person's machines, as the network's hub lists them (plan L34, step M2: GET /api/v1/machines). Only the caller's own machines are ever in it.
+ */
+export interface NetworkMachineListResult {
+ machines: NetworkMachine[];
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.machine-manage-result@1: the machine as it is after a change (plan L34, step M2: PATCH /api/v1/machines/:id, PUT /api/v1/machines/:id/roles, POST /api/v1/machines/:id/revoke). The pairing route answers network.machine-pairing@1 instead, because a pairing is a code and not a machine.
+ */
+export interface NetworkMachineManageResult {
+ machine: NetworkMachine;
+}
+
+/** [email protected] (owner: bot) */
+/**
+ * bot.machine-manage-request@1: what a caller changes on a machine (plan L34, step M2). A rename gives a name; a roles change gives the whole set of roles (network.machine-roles-put@1's shape). The same body is what the network's hub sends to Bot, so both sides speak one shape.
+ */
+export type BotMachineManageRequest = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * The name the owner gives the machine. Absent when the call changes something else.
+ */
+ name?: string;
+ /**
+ * The machine's roles after the change. Absent when the call changes something else.
+ *
+ * @maxItems 8
+ */
+ roles?:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string];
+};
+
+/** [email protected] (owner: bot) */
+/**
+ * bot.machine-manage-result@1: the machine as it is after the change (plan L34, step M2). Bot is the registry underneath: it answers with its own device record, never with a credential.
+ */
+export interface BotMachineManageResult {
+ machine: BotDevice;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.machine-role@1: one role a machine can have (plan L34, step M4). A role is what the machine does for the network: a name for the owner, what it installs on the machine and what the hub shows. The catalogue is network.machine-roles@1; a role outside it is refused.
+ */
+export interface NetworkMachineRole {
+ /**
+ * The role's id: the product that owns it, then what it is (codes.worker, desks.host, ai.host, bot.robot).
+ */
+ id: string;
+ /**
+ * The role's name, in the owner's words.
+ */
+ name: string;
+ /**
+ * What the role puts on the machine (the job runner, the desk sandbox, the local model server).
+ */
+ installs: string;
+ /**
+ * What the hub shows for a machine with this role.
+ */
+ shows: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.machine-roles@1: the catalogue of machine roles (plan L34, step M4). A product declares a role — a name, what it installs on the machine and what the hub shows — and the Node turns it on at its next check-in. The first roles are Codes' worker, Desks' host, AI's local models and Bot's robot. A role outside this catalogue is refused (network.machine-roles-put@1).
+ */
+export interface NetworkMachineRoles {
+ /**
+ * @minItems 1
+ */
+ roles: [NetworkMachineRole, ...NetworkMachineRole[]];
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.machine-roles-put@1: the roles the owner gives a machine (PUT /api/v1/machines/:id/roles, capability machines.manage). The whole set, not a change: what is not named is turned off. Every id must be in the catalogue (network.machine-roles@1); an unknown role is refused and nothing is changed.
+ */
+export interface NetworkMachineRolesPut {
+ /**
+ * The machine's roles after the change. An empty array turns every role off.
+ *
+ * @maxItems 8
+ */
+ roles:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string];
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.machine-pairing@1: a one-time code that pairs a machine with the person's account (POST /api/v1/machines/pairings, capability machines.manage). The code is shown once, expires, and works once: the machine redeems it and becomes the person's. The command is what the owner runs on the machine (openvibe.bot/install). The code is never stored in the clear and never returned again.
+ */
+export interface NetworkMachinePairing {
+ /**
+ * The one-time pairing code, eight Crockford base32 characters in two groups (XXXX-XXXX).
+ */
+ code: string;
+ /**
+ * The pairing's id, as the install command carries it. Absent when the authority behind it does not name one.
+ */
+ pairing_id?: string;
+ /**
+ * When the code stops working.
+ */
+ expires_at: string;
+ /**
+ * The command the owner runs on the machine (openvibe.bot/install, with the network, the pairing id and the code).
+ */
+ installer: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.machine.online v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person (a Bot device of kind machine) came online: it checked in and is connected to Bot. Network republishes Bot's change (Bot reports it to Network, which writes this event), so a consumer of the network's events hears about a machine once, in one place. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.
+ */
+export interface NetworkMachineOnlinePayload {
+ machine_id: string;
+ /**
+ * The machine's node principal on Network, when it has one.
+ */
+ principal_id?: string;
+ /**
+ * The person the machine belongs to.
+ */
+ owner: string;
+ at: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.machine.offline v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person went offline: it is no longer connected to Bot. Network republishes Bot's change, so a consumer of the network's events hears about a machine once, in one place. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.
+ */
+export interface NetworkMachineOfflinePayload {
+ machine_id: string;
+ /**
+ * The machine's node principal on Network, when it has one.
+ */
+ principal_id?: string;
+ /**
+ * The person the machine belongs to.
+ */
+ owner: string;
+ at: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.machine.paired v1 (OpenVibe.Network, plan L34: the machines hub). A machine was paired with a person's account: it redeemed the one-time code and became theirs. It holds a credential from then on; the code is spent. The machine's device id in Bot is absent until the machine checks in for the first time. Envelope: subject { type: machine, id: <nod_…> }, visibility internal, actor service:network.
+ */
+export interface NetworkMachinePairedPayload {
+ /**
+ * The machine's device id in Bot's registry, when Bot already knows the device.
+ */
+ machine_id?: string;
+ /**
+ * The machine's node id (platform_node_principals.node_id).
+ */
+ node_id?: string;
+ /**
+ * The node principal the pairing created. Also the envelope subject id.
+ */
+ principal_id: string;
+ /**
+ * The person the machine belongs to.
+ */
+ owner: string;
+ at: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.machine.roles_changed v1 (OpenVibe.Network, plan L34, step M4). A machine's roles changed: the whole set after the change, from the catalogue (network.machine-roles@1). The machine applies them at its next check-in, so this event says what was asked for, not what the machine has already done. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.
+ */
+export interface NetworkMachineRolesChangedPayload {
+ machine_id: string;
+ /**
+ * The person the machine belongs to.
+ */
+ owner: string;
+ /**
+ * The machine's roles after the change.
+ */
+ roles: string[];
+ /**
+ * Who asked for the change: the person, or an app acting with their consent.
+ */
+ changed_by?: string;
+ at: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.machine.revoked v1 (OpenVibe.Network, plan L34: the machines hub). A machine of a person was revoked: it is no longer theirs and its credential is dead. The machine's credential and its hash are never carried. A second revoke changes nothing and emits nothing. Envelope: subject { type: machine, id: <dev_…> }, visibility internal, actor service:network.
+ */
+export interface NetworkMachineRevokedPayload {
+ machine_id: string;
+ /**
+ * The machine's node principal on Network, when it had one.
+ */
+ principal_id?: string;
+ /**
+ * The person the machine belonged to.
+ */
+ owner: string;
+ /**
+ * Why it was revoked, when the caller gave one. Absent otherwise.
+ */
+ reason?: string;
+ at: string;
+}
+
/** [email protected] (owner: network) */
/** [email protected] (owner: network) */
/**
/**
* network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.
* network.avatar-write-request@1: the body of POST /internal/user-avatar on OpenVibe.Network (capability network.avatar.write, held by the site a person picked the avatar on; Live posts here through its notify helper). Adopts the picture as the account's avatar and fans it out to the other sites. Only an openvibe.media address is accepted, so an avatar can never be a tracking pixel or a third-party file. Refusals are { error }: 400 without a user_id, 404 for an unknown user, 422 for anything but an openvibe.media address, 503 while the avatar service is down.
@@ -203351,6 +203642,14 @@ export interface BotDevice {
@@ -203351,6 +203642,14 @@ export interface BotDevice {
created_at: string;
created_at: string;
updated_at: string;
updated_at: string;
online?: boolean;
online?: boolean;
+ /**
+ * The machine roles the owner set (plan L34: the catalogue is network.machine-roles@1). Absent on a device that has none.
+ */
+ roles?: string[];
+ /**
+ * The operating system the device reported.
+ */
+ os?: string | null;
}
}
/** [email protected] (owner: bot) */
/** [email protected] (owner: bot) */
new file mode 100644
new file mode 100644
@@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
+{
+ "id": "bot.machine.manage",
+ "version": "1.0",
+ "owner": "bot",
+ "status": "active",
+ "visibility": "first-party",
+ "sensitive": true,
+ "description": "A service acts for a person on their machines through Bot's machines API: it lists, renames, gives roles to and revokes the machines of the person it names (X-OV-Subject). Network holds it for the machines hub (openvibe.network/machines): Bot stays the registry underneath, so Bot checks that the machine is the named person's own. Never grantable to apps.",
+ "permissions": [
+ "bot:machine:read",
+ "bot:machine:rename",
+ "bot:machine:roles",
+ "bot:machine:revoke"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "bot-machine",
+ "events": [],
+ "implementedBy": [
+ "GET /api/v1/machines",
+ "GET /api/v1/machines/:id",
+ "PATCH /api/v1/machines/:id",
+ "PUT /api/v1/machines/:id/roles",
+ "POST /api/v1/machines/:id/revoke"
+ ],
+ "inputSchema": "bot.machine-manage-request@1",
+ "outputSchema": "bot.machine-manage-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
+{
+ "id": "network.machines.manage",
+ "version": "1.0",
+ "owner": "network",
+ "status": "active",
+ "visibility": "public",
+ "sensitive": true,
+ "description": "Pair, rename, give roles to and revoke a person's own machines. Bot stays the registry underneath: Network asks Bot as the person, and Bot checks that the machine is theirs. An app needs the person's consent for this scope, and may then do only what the person could.",
+ "permissions": [
+ "machines:pair",
+ "machines:rename",
+ "machines:roles",
+ "machines:revoke"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "network-write",
+ "events": [
+ "network.machine.paired",
+ "network.machine.roles_changed",
+ "network.machine.revoked"
+ ],
+ "implementedBy": [
+ "POST /api/v1/machines/pairings",
+ "PATCH /api/v1/machines/:id",
+ "PUT /api/v1/machines/:id/roles",
+ "POST /api/v1/machines/:id/revoke"
+ ],
+ "inputSchema": "bot.machine-manage-request@1",
+ "outputSchema": "network.machine-manage-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "network.machines.read",
+ "version": "1.0",
+ "owner": "network",
+ "status": "active",
+ "visibility": "public",
+ "description": "Read a person's own machines: the list, one machine, and the catalogue of roles. A person reads their own with their own token; an app needs the person's consent for this scope (it is a public capability, so it can be granted to an app, and the app then acts only on behalf of the person who consented).",
+ "permissions": [
+ "machines:read"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "network-read",
+ "events": [],
+ "implementedBy": [
+ "GET /api/v1/machines",
+ "GET /api/v1/machines/:id",
+ "GET /api/v1/machines/roles"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "network.machine-list-result@1"
+}
@@ -116,5 +116,13 @@
@@ -116,5 +116,13 @@
}
}
],
],
"neighbourhood": "build"
"neighbourhood": "build"
- }
+ },
+ "machineRoles": [
+ {
+ "id": "ai.host",
+ "name": "AI host",
+ "installs": "the local model server (Ollama, LM Studio) as OpenVibe Node's ai plugin",
+ "shows": "Serves your own AI models, on your own machine."
+ }
+ ]
}
}
@@ -37,7 +37,8 @@
@@ -37,7 +37,8 @@
"bot.job.dispatch",
"bot.job.dispatch",
"bot.resource.read",
"bot.resource.read",
"bot.resource.control",
"bot.resource.control",
- "bot.desk.session"
+ "bot.desk.session",
+ "bot.machine.manage"
],
],
"eventsProduced": [
"eventsProduced": [
"bot.robot.online",
"bot.robot.online",
@@ -104,5 +105,13 @@
@@ -104,5 +105,13 @@
"none": "no expiring work claims: PostgreSQL row locks serialize queue changes and an advisory lock serializes boot migrations; WebSocket sessions are in memory"
"none": "no expiring work claims: PostgreSQL row locks serialize queue changes and an advisory lock serializes boot migrations; WebSocket sessions are in memory"
}
}
},
},
- "notes": "Robot and device control service on openvibe-ovh since 2026-10-02; the public site uses HTTPS while health and readiness are loopback-only. Non-HTTP bindings: the device WebSocket /device carries bot.device-message@1 frames (and platform.job-frame@1 jobs); the operator WebSocket /control carries bot.command@1, answered by bot.command-result@1."
+ "notes": "Robot and device control service on openvibe-ovh since 2026-10-02; the public site uses HTTPS while health and readiness are loopback-only. Non-HTTP bindings: the device WebSocket /device carries bot.device-message@1 frames (and platform.job-frame@1 jobs); the operator WebSocket /control carries bot.command@1, answered by bot.command-result@1.",
+ "machineRoles": [
+ {
+ "id": "bot.robot",
+ "name": "Robot",
+ "installs": "the robot drivers the profile asks for",
+ "shows": "Drives a robot, and takes its commands."
+ }
+ ]
}
}
@@ -109,5 +109,13 @@
@@ -109,5 +109,13 @@
"none": "no claims: one process owns the ov_codes PostgreSQL database; writes are transactions there"
"none": "no claims: one process owns the ov_codes PostgreSQL database; writes are transactions there"
}
}
},
},
- "notes": "Port 4900, unit openvibe-codes.service. The open coding-agent harness (owner 2026-10-08): the harness catalog and router (GET /api/v1/harnesses, POST /api/v1/harnesses/route), the way to improve OpenVibe (contributing, code of conduct, the contributor ladder) and permanent redirects from the former developer-portal paths to openvibe.services, where the portal moved (manifests, releases, trust, playgrounds, docs, OAuth and webhook tools). Coding tasks on the person's own machine (plan T16) are Run jobs: their sessions are kept line by line in codes_session_lines as the stream arrives (back-filled from the machine's final log; GET /run/tasks/:id/session.json replays them), run.job.* events from OpenVibe.Run finalize a task without polling, and every state change is published as codes.task.queued|running|succeeded|failed|cancelled from a transactional outbox."
+ "notes": "Port 4900, unit openvibe-codes.service. The open coding-agent harness (owner 2026-10-08): the harness catalog and router (GET /api/v1/harnesses, POST /api/v1/harnesses/route), the way to improve OpenVibe (contributing, code of conduct, the contributor ladder) and permanent redirects from the former developer-portal paths to openvibe.services, where the portal moved (manifests, releases, trust, playgrounds, docs, OAuth and webhook tools). Coding tasks on the person's own machine (plan T16) are Run jobs: their sessions are kept line by line in codes_session_lines as the stream arrives (back-filled from the machine's final log; GET /run/tasks/:id/session.json replays them), run.job.* events from OpenVibe.Run finalize a task without polling, and every state change is published as codes.task.queued|running|succeeded|failed|cancelled from a transactional outbox.",
+ "machineRoles": [
+ {
+ "id": "codes.worker",
+ "name": "Codes worker",
+ "installs": "the Codes job runner (the coding agent and the job sandbox)",
+ "shows": "Runs your coding jobs, such as a task you start on openvibe.codes."
+ }
+ ]
}
}
@@ -80,7 +80,9 @@
@@ -80,7 +80,9 @@
"network.status.incident",
"network.status.incident",
"network.actions.read",
"network.actions.read",
"network.consent.read",
"network.consent.read",
- "network.consent.manage"
+ "network.consent.manage",
+ "network.machines.read",
+ "network.machines.manage"
],
],
"eventsProduced": [
"eventsProduced": [
"network.account.deleted",
"network.account.deleted",
@@ -102,7 +104,12 @@
@@ -102,7 +104,12 @@
"network.subject.merged",
"network.subject.merged",
"network.user.token_valid_after",
"network.user.token_valid_after",
"network.user.updated",
"network.user.updated",
- "network.consent.changed"
+ "network.consent.changed",
+ "network.machine.online",
+ "network.machine.offline",
+ "network.machine.paired",
+ "network.machine.roles_changed",
+ "network.machine.revoked"
],
],
"eventsConsumed": [
"eventsConsumed": [
"billing.staff.action",
"billing.staff.action",
@@ -139,5 +139,13 @@
@@ -139,5 +139,13 @@
],
],
"keywords": "openvibe run, job runner, run jobs on nodes, sandbox api, per-second metering",
"keywords": "openvibe run, job runner, run jobs on nodes, sandbox api, per-second metering",
"neighbourhood": "build"
"neighbourhood": "build"
- }
+ },
+ "machineRoles": [
+ {
+ "id": "desks.host",
+ "name": "Desk host",
+ "installs": "the desk sandbox (a private browser desk for every session)",
+ "shows": "Hosts desks for agents, yours and other people's."
+ }
+ ]
}
}
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.260.0",
+ "version": "0.261.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.260.0",
+ "version": "0.261.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.260.0",
+ "version": "0.261.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",
@@ -12,7 +12,9 @@ const c = require('..');
@@ -12,7 +12,9 @@ const c = require('..');
// bot.resource.control: an owner stops or resumes their robot from OpenVibe.Services (0.176.0, ADR-048 step 8 phase 2).
// bot.resource.control: an owner stops or resumes their robot from OpenVibe.Services (0.176.0, ADR-048 step 8 phase 2).
// bot.machine.ai: a model on the person's own machine, for that person (0.219.0, the AI-on-your-machines plan).
// bot.machine.ai: a model on the person's own machine, for that person (0.219.0, the AI-on-your-machines plan).
// bot.desk.session: Run's sessions lease one desk each (0.223.0, the Desks plan's D6).
// bot.desk.session: Run's sessions lease one desk each (0.223.0, the Desks plan's D6).
-const BOT_CAPABILITIES = ['bot.robot.read', 'bot.robot.manage', 'bot.robot.control', 'bot.computer.control', 'bot.machine.ai', 'bot.desk.session', 'bot.device.connect', 'bot.job.dispatch', 'bot.resource.read', 'bot.resource.control'];
+// bot.machine.manage: the machines hub (0.258.0, plan L34): Network fronts Bot for a person and for the apps that
+// person consented to; Bot stays the authority and checks the machine is that person's own.
+const BOT_CAPABILITIES = ['bot.robot.read', 'bot.robot.manage', 'bot.robot.control', 'bot.computer.control', 'bot.machine.ai', 'bot.machine.manage', 'bot.desk.session', 'bot.device.connect', 'bot.job.dispatch', 'bot.resource.read', 'bot.resource.control'];
const BOT_EVENTS = ['bot.robot.online', 'bot.robot.offline', 'bot.estop.set', 'bot.estop.cleared', 'bot.command.refused'];
const BOT_EVENTS = ['bot.robot.online', 'bot.robot.offline', 'bot.estop.set', 'bot.estop.cleared', 'bot.command.refused'];
const bot = c.services.get('bot');
const bot = c.services.get('bot');
new file mode 100644
new file mode 100644
@@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
+'use strict';
+// The machines hub (plan L34, steps M2 and M4): a person's machines are the network's, Bot stays the registry
+// underneath, and a machine's roles come from a catalogue a product declares in its own manifest. The schemas
+// carry the shapes; the rules they cannot say are checked here: one catalogue, one id shape everywhere, and a
+// role a product declared is always a role the hub accepts.
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const c = require('..');
+
+const ROOT = path.join(__dirname, '..');
+const schema = (f) => JSON.parse(fs.readFileSync(path.join(ROOT, 'contracts', f), 'utf8'));
+const manifest = (id) => JSON.parse(fs.readFileSync(path.join(ROOT, 'manifests', 'services', `${id}.json`), 'utf8'));
+const cap = (id) => JSON.parse(fs.readFileSync(path.join(ROOT, 'manifests', 'capabilities', `${id}.json`), 'utf8'));
+
+const ROLES = {
+ 'codes.worker': 'codes',
+ 'desks.host': 'run',
+ 'ai.host': 'ai',
+ 'bot.robot': 'bot',
+};
+
+// One catalogue: every product that declares a role declares it once, and nobody declares another's.
+const declared = new Map();
+for (const m of c.services.manifests) {
+ for (const role of m.machineRoles || []) {
+ assert.ok(!declared.has(role.id), `${role.id} is declared once (${m.id} and ${declared.get(role.id)})`);
+ declared.set(role.id, m.id);
+ assert.ok(role.name && role.installs && role.shows, `${role.id} says what it is, what it installs and what it shows`);
+ }
+}
+assert.deepStrictEqual(Object.keys(ROLES).sort(), [...declared.keys()].sort(), 'the catalogue holds the four first roles');
+for (const [id, owner] of Object.entries(ROLES)) assert.strictEqual(declared.get(id), owner, `${id} is ${owner}'s role`);
+
+// One id shape everywhere: a machine, the catalogue and a request all name a role the same way.
+const machine = schema('network/machine.v1.json');
+const role = schema('network/machine-role.v1.json');
+const roles = schema('network/machine-roles.v1.json');
+const put = schema('network/machine-roles-put.v1.json');
+const pattern = role.properties.id.pattern;
+assert.strictEqual(machine.properties.roles.items.pattern, pattern, 'a machine names its roles the same way');
+assert.strictEqual(put.properties.roles.items.pattern, pattern, 'a request names its roles the same way');
+for (const id of Object.keys(ROLES)) {
+ assert.ok(new RegExp(pattern).test(id), `${id} is a role id`);
+ assert.ok(c.validate('network.machine-role@1', { id, name: 'x', installs: 'y', shows: 'z' }).valid, `${id} is a role`);
+}
+
+// The catalogue answers with the roles, and only with them.
+const catalogue = { roles: [...declared.keys()].map((id) => ({ id, name: 'x', installs: 'y', shows: 'z' })) };
+assert.ok(c.validate('network.machine-roles@1', catalogue).valid, 'the catalogue answers with its roles');
+assert.ok(!c.validate('network.machine-roles@1', { roles: [] }).valid, 'an empty catalogue is no catalogue');
+assert.ok(!c.validate('network.machine-roles-put@1', { roles: ['not-a-role'] }).valid, 'a request names roles that exist as ids');
+
+// A machine is a machine: an id, a kind, whether it is online, its roles and what it does now.
+for (const k of ['id', 'kind', 'online', 'roles', 'running']) assert.ok(machine.required.includes(k), `network.machine@1 needs ${k}`);
+assert.deepStrictEqual(machine.properties.running.items.properties.kind.enum, ['codes.jobs', 'desks', 'models', 'robots'],
+ 'a machine says what it does now: its jobs, its desks, its models and its robots');
+assert.strictEqual(machine.additionalProperties, false, 'a machine carries nothing else');
+
+// A pairing is a code, an expiry and the command to run; the code is shown once.
+for (const k of ['code', 'expires_at', 'installer']) assert.ok(schema('network/machine-pairing.v1.json').required.includes(k), `the pairing needs ${k}`);
+
+// The capabilities: the hub's two are public (an app may hold them with the person's consent), Bot's is not.
+assert.strictEqual(cap('network.machines.read').visibility, 'public');
+assert.strictEqual(cap('network.machines.read').owner, 'network');
+assert.strictEqual(cap('network.machines.manage').visibility, 'public');
+assert.strictEqual(cap('network.machines.manage').owner, 'network');
+assert.strictEqual(cap('network.machines.manage').sensitive, true, 'revoking a machine is an act with an effect outside');
+assert.strictEqual(cap('bot.machine.manage').visibility, 'first-party');
+assert.strictEqual(cap('bot.machine.manage').owner, 'bot');
+
+// The manifests say who serves what.
+const network = manifest('network');
+assert.ok(network.capabilities.includes('network.machines.read') && network.capabilities.includes('network.machines.manage'));
+for (const e of ['network.machine.online', 'network.machine.offline', 'network.machine.paired', 'network.machine.roles_changed', 'network.machine.revoked']) {
+ assert.ok(network.eventsProduced.includes(e), `network produces ${e}`);
+}
+assert.ok(manifest('bot').capabilities.includes('bot.machine.manage'), 'bot serves bot.machine.manage');
+
+// The fixtures: each valid one validates, each invalid one does not.
+for (const id of ['network.machine', 'network.machine-role', 'network.machine-roles', 'network.machine-roles-put', 'network.machine-pairing',
+ 'network.machine-list-result', 'network.machine-manage-result', 'bot.machine-manage-request', 'bot.machine-manage-result',
+ 'network.machine.online', 'network.machine.offline', 'network.machine.paired', 'network.machine.roles_changed', 'network.machine.revoked']) {
+ for (const kind of ['valid', 'invalid']) {
+ for (const f of fs.readdirSync(path.join(ROOT, 'fixtures', id, kind))) {
+ const value = JSON.parse(fs.readFileSync(path.join(ROOT, 'fixtures', id, kind, f), 'utf8'));
+ const r = c.validate(`${id}@1`, value);
+ assert.strictEqual(r.valid, kind === 'valid', `${id} ${kind}/${f}: ${JSON.stringify(r.errors)}`);
+ }
+ }
+}
+
+console.log('network.machine@1: one catalogue, one role id shape, four roles, public hub capabilities, fixtures');
@@ -1,10 +1,16 @@
@@ -1,10 +1,16 @@
'use strict';
'use strict';
// ADR-005 amendment 1: people's presence is ephemeral, in Chat's delivery plane.
// ADR-005 amendment 1: people's presence is ephemeral, in Chat's delivery plane.
// ADR-043 records robot connectivity in Events; it is not a person's presence.
// ADR-043 records robot connectivity in Events; it is not a person's presence.
+// The machines hub (plan L34) records the same for a person's machines: a machine coming online is the
+// device changing state, never the person being present. Any other `.online`, `.offline` or presence event
+// type is still refused.
const assert = require('assert');
const assert = require('assert');
const c = require('..');
const c = require('..');
const produced = c.services.manifests.flatMap((m) => m.eventsProduced || []);
const produced = c.services.manifests.flatMap((m) => m.eventsProduced || []);
-const robotConnectivity = new Set(['bot.robot.online', 'bot.robot.offline']);
-const presenceEvents = produced.filter((t) => /presence|\.online|\.offline/i.test(t) && !robotConnectivity.has(t));
+const deviceConnectivity = new Set([
+ 'bot.robot.online', 'bot.robot.offline',
+ 'network.machine.online', 'network.machine.offline',
+]);
+const presenceEvents = produced.filter((t) => /presence|\.online|\.offline/i.test(t) && !deviceConnectivity.has(t));
assert.ok(presenceEvents.length === 0, `no person presence event type: ${presenceEvents}`);
assert.ok(presenceEvents.length === 0, `no person presence event type: ${presenceEvents}`);
console.log('presence policy: all checks passed');
console.log('presence policy: all checks passed');
@@ -929,6 +929,8 @@ sub(path.join(__dirname, 'resources.test.js'));
@@ -929,6 +929,8 @@ sub(path.join(__dirname, 'resources.test.js'));
// The wait record (plan A2): a wait is a record, not a running loop.
// The wait record (plan A2): a wait is a record, not a running loop.
sub(path.join(__dirname, 'actor-wait.test.js'));
sub(path.join(__dirname, 'actor-wait.test.js'));
sub(path.join(__dirname, 'network-consent.test.js'));
sub(path.join(__dirname, 'network-consent.test.js'));
+// The machines hub (plan L34): one catalogue of roles, declared by the products that own them.
+sub(path.join(__dirname, 'machines.test.js'));
// Labels and choices (L1): one label list, one choice list, and the AI's suggestion stored as suggested.
// Labels and choices (L1): one label list, one choice list, and the AI's suggestion stored as suggested.
sub(path.join(__dirname, 'content-labels.test.js'));
sub(path.join(__dirname, 'content-labels.test.js'));
// Documentation currency (roadmap WS-U task 3): a placeholder service with no repository yet is skipped, not a gap.
// Documentation currency (roadmap WS-U task 3): a placeholder service with no repository yet is skipped, not a gap.