---
title: "common.control-lease@1 · OpenVibe.Services"
canonical: "https://openvibe.services/docs/contracts/common.control-lease"
updated: null
---

# common.control-lease@1 · OpenVibe.Services

HTML: [common.control-lease@1 · OpenVibe.Services](https://openvibe.services/docs/contracts/common.control-lease)

# ControlLease `common.control-lease@1`

Generated at 2026-11-13 06:25:48 UTC from [`openvibe-contracts v0.419.0`](https://github.com/OpenVibers/OpenVibe.Contracts/tree/v0.419.0) and [`openvibe-sdk v0.161.0`](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.161.0).

- Version: 1.0.0
- Owner: contracts
- Visibility: public
- Status: active
- Compatibility: backward
- Schema: [`https://openvibe.network/contracts/common/control-lease.v1.json`](/docs/contracts/common.control-lease.json)

common.control-lease@1: who holds the controls of a shared thing (seven domains plan B3). The state names the thing, its holder (a person, an AI, or nobody when the controls are free), when the holder got them, an optional time limit as an absolute epoch instant, the subjects watching and a sequence number. Every control change, including an expiry or a take-back, increments seq; a change in watchers alone does not. The authority that owns the thing writes and enforces the state: it decides who may request, grant, release or take the controls, and take-back always wins. Chat hosts a room's lease on its room socket and names the thing chat.room:<slug>; the holder is the person or AI whose subject Chat would accept on a set.

## Fields

| Field | Type | Required | Description | Constraints |
| --- | --- | --- | --- | --- |
| `thing` | string | yes | The shared thing the lease controls, in the owning authority's own namespace. Chat writes chat.room:<slug>. | - `minLength 1`<br>- `maxLength 200` |
| `holder` | object | yes | Who may act on the thing now. nobody means the controls are free; subject is null exactly then. | - `no other fields` |
| `holder.kind` | enum | yes | A signed-in person, an AI agent the authority can name, or nobody. | - `one of "person", "ai", "nobody"` |
| `holder.subject` | string \| null | yes | The authority's subject for the holder (a Network usr\_… subject for a person, an agent subject for an AI); null when the kind is nobody. | - `minLength 1`<br>- `maxLength 200` |
| `since_ms` | integer | yes | When the current holder got the controls, as epoch milliseconds in the authority's clock. | - `minimum 0` |
| `expires_ms` | integer \| null |  | When the holder's time runs out, as epoch milliseconds; null or absent means no time limit. At that instant the authority makes the holder nobody and increments seq. | - `minimum 0` |
| `watchers` | array of string | yes | The subjects watching the thing now, as live presence rather than a durable membership list. The authority decides who is listed; Chat lists the signed-in subjects connected to the room. A change here does not increment seq. | - `maxItems 500`<br>- `unique items`<br>- `items: minLength 1`<br>- `items: maxLength 200` |
| `seq` | integer | yes | The control version: starts at 1 for a thing's first state and increments on every holder change, expiry or take-back. A client ignores a state with a seq it has already seen. | - `minimum 1` |

## Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

 Valid: ai

~~~~
{
  "thing": "desk:desk-7",
  "holder": {
    "kind": "ai",
    "subject": "ai:vibe"
  },
  "since_ms": 1794067200000,
  "expires_ms": null,
  "watchers": [],
  "seq": 1
}
~~~~

Valid: free

~~~~
{
  "thing": "chat.room:friday-film",
  "holder": {
    "kind": "nobody",
    "subject": null
  },
  "since_ms": 1794067200000,
  "watchers": [
    "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
  ],
  "seq": 5
}
~~~~

Valid: person-with-limit

~~~~
{
  "thing": "chat.room:friday-film",
  "holder": {
    "kind": "person",
    "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
  },
  "since_ms": 1794067200000,
  "expires_ms": 1794070800000,
  "watchers": [
    "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",
    "usr_01JAB2C3D4E5F6G7H8J9K0MNPX"
  ],
  "seq": 4
}
~~~~

Rejected: duplicate-watcher

~~~~
{
  "thing": "chat.room:friday-film",
  "holder": {
    "kind": "person",
    "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
  },
  "since_ms": 1794067200000,
  "watchers": [
    "usr_01JAB2C3D4E5F6G7H8J9K0MNPS",
    "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
  ],
  "seq": 1
}
~~~~

Rejected: nobody-with-subject

~~~~
{
  "thing": "chat.room:friday-film",
  "holder": {
    "kind": "nobody",
    "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
  },
  "since_ms": 1794067200000,
  "watchers": [],
  "seq": 1
}
~~~~

Rejected: person-without-subject

~~~~
{
  "thing": "chat.room:friday-film",
  "holder": {
    "kind": "person"
  },
  "since_ms": 1794067200000,
  "watchers": [],
  "seq": 1
}
~~~~

Rejected: seq-zero

~~~~
{
  "thing": "chat.room:friday-film",
  "holder": {
    "kind": "person",
    "subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
  },
  "since_ms": 1794067200000,
  "watchers": [],
  "seq": 0
}
~~~~

## Validate

~~~~
const contracts = require('openvibe-contracts');
contracts.validate('common.control-lease@1', value);   // { valid, errors: [{ path, message }] }
~~~~
