openvibers/openvibe.contracts Commits

Add watch id prefixes, harness capability keys and resource summary ovrn (#21)

OP OpenVibers committed 3 Oct 2026 · bf6b423931 · 12 files, +244 −4

local checks ✓ 3/3 · CI ✓ 3/3 · review SHIP by deepseek-v4.1-flash-fast

https://github.com/OpenVibers/OpenVibe.Contracts/pull/21

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 17151831..a60dd572 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -8,6 +8,23 @@ the notes were in the tag and commit messages (`git tag -n1`).
- `network.project.read` is active, implemented by OpenVibe.Network `GET /internal/projects/:project_id`.
++**Watch ids** (plan T18, ready now):
++- `lib/ids.js`: `PREFIX.watch = 'wch'`, `PREFIX.observation = 'wco'` and `PREFIX.check = 'ckr'`, so `ids.newId('watch')`
++ gives the `wch_<ULID>` id the `common.resource-summary` fixture `watch` already carries. None is a subject type or a
++ principal.
++
++**Harness capability vocabulary** (plan T17, ready now):
++- `platform.harness-offer@1` `capabilities` gains the optional booleans `edit`, `review`, `tools`, `vision`, `browser`
++ and `computer_use`, and an optional `runtimes` array (unique; `function`, `code`, `browser`, `linux`, `desktop`,
++ `gpu`, the `platform.runtime-class@1` classes). `capabilities` keeps `additionalProperties: false`, and the entry stays
++ 1.0.0, as it did when 0.86.0 added optional fields. New fixtures `capability-vocabulary` (valid, every new key) and
++ `unknown-capability` (invalid, a `gpu` key: a runtime class belongs in `runtimes`). Additive.
++
++**Resource names in the resource index** (plan T13, ready now):
++- `common.resource-summary@1` is 1.1.0: an optional `ovrn` (ADR-034 section 2) with the pattern of
++ `common.usage-recorded@1` `resource`, naming the summary's own service, project and id. New fixtures `with-ovrn`
++ (valid) and `ovrn-without-project` (invalid). Additive.
++
## 0.87.0 — 2026-10-03
**Host git sources** (plan T12 Stage B): a Host site may name a public Git source — provider, repository URL and branch;
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex 49b291d8..a47dd888 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -5514,7 +5514,7 @@
},
{
"id": "common.resource-summary",
-- "version": "1.0.0",
++ "version": "1.1.0",
"owner": "contracts",
"schema": "common/resource-summary.v1.json",
"visibility": "public",
ddiff --git a/contracts/common/resource-summary.v1.json b/contracts/common/resource-summary.v1.json
iindex 7496cad1..5a06268e 100644
---- a/contracts/common/resource-summary.v1.json
++++ b/contracts/common/resource-summary.v1.json
@@@ -27,6 +27,12 @@
"type": "string",
"pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
},
++ "ovrn": {
++ "type": "string",
++ "maxLength": 200,
++ "pattern": "^ovrn:[a-z][a-z0-9-]{0,31}:prj_[0-9A-HJKMNP-TV-Z]{26}:[a-z][a-z0-9-]{0,39}/[a-z]{3}_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Optional resource name (ADR-034 section 2), the name grants, audit, events, usage records and bills use: ovrn:<service>:<project_id>:<type>/<id>, e.g. ovrn:watch:<project_id>:watch/<wch_id>, with the pattern of common.usage-recorded@1 resource. Its service, project and id are this summary's service, project_id and id. Added in 1.1.0."
++ },
"owner": {
"$ref": "../identity/subject-ref.v1.json"
},
ddiff --git a/contracts/platform/harness-offer.v1.json b/contracts/platform/harness-offer.v1.json
iindex d92777cf..8e85b590 100644
---- a/contracts/platform/harness-offer.v1.json
++++ b/contracts/platform/harness-offer.v1.json
@@@ -40,6 +40,46 @@
},
"resume": {
"type": "boolean"
++ },
++ "edit": {
++ "type": "boolean",
++ "description": "edits files in its working tree"
++ },
++ "review": {
++ "type": "boolean",
++ "description": "reviews a change and reports findings"
++ },
++ "tools": {
++ "type": "boolean",
++ "description": "calls tools such as a shell, search, or file reads on its own"
++ },
++ "vision": {
++ "type": "boolean",
++ "description": "reads images such as screenshots"
++ },
++ "browser": {
++ "type": "boolean",
++ "description": "drives a web browser"
++ },
++ "computer_use": {
++ "type": "boolean",
++ "description": "operates a desktop through its screen, keyboard, and mouse"
++ },
++ "runtimes": {
++ "type": "array",
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "enum": [
++ "function",
++ "code",
++ "browser",
++ "linux",
++ "desktop",
++ "gpu"
++ ]
++ },
++ "description": "platform.runtime-class@1 classes the harness runs in"
}
},
"required": [
ddiff --git a/fixtures/common.resource-summary/invalid/ovrn-without-project.json b/fixtures/common.resource-summary/invalid/ovrn-without-project.json
nnew file mode 100644
iindex 00000000..3b6801f8
---- /dev/null
++++ b/fixtures/common.resource-summary/invalid/ovrn-without-project.json
@@@ -0,0 +1,20 @@
++{
++ "id": "wch_01J0000000000000000000000A",
++ "kind": "watch.watch",
++ "service": "watch",
++ "project_id": "prj_01J0000000000000000000000A",
++ "ovrn": "ovrn:watch:watch/wch_01J0000000000000000000000A",
++ "owner": {
++ "type": "user",
++ "id": "usr_01J0000000000000000000000A"
++ },
++ "name": "Price of a camera",
++ "state": "active",
++ "created_at": "2026-09-28T23:00:00Z",
++ "usage": {
++ "watch-check": 120
++ },
++ "links": {
++ "self": "https://openvibe.watch/api/v1/watches/wch_01J0000000000000000000000A"
++ }
++}
ddiff --git a/fixtures/common.resource-summary/valid/with-ovrn.json b/fixtures/common.resource-summary/valid/with-ovrn.json
nnew file mode 100644
iindex 00000000..e177c87a
---- /dev/null
++++ b/fixtures/common.resource-summary/valid/with-ovrn.json
@@@ -0,0 +1,20 @@
++{
++ "id": "wch_01J0000000000000000000000A",
++ "kind": "watch.watch",
++ "service": "watch",
++ "project_id": "prj_01J0000000000000000000000A",
++ "ovrn": "ovrn:watch:prj_01J0000000000000000000000A:watch/wch_01J0000000000000000000000A",
++ "owner": {
++ "type": "user",
++ "id": "usr_01J0000000000000000000000A"
++ },
++ "name": "Price of a camera",
++ "state": "active",
++ "created_at": "2026-09-28T23:00:00Z",
++ "usage": {
++ "watch-check": 120
++ },
++ "links": {
++ "self": "https://openvibe.watch/api/v1/watches/wch_01J0000000000000000000000A"
++ }
++}
ddiff --git a/fixtures/platform.harness-offer/invalid/unknown-capability.json b/fixtures/platform.harness-offer/invalid/unknown-capability.json
nnew file mode 100644
iindex 00000000..9255c1ce
---- /dev/null
++++ b/fixtures/platform.harness-offer/invalid/unknown-capability.json
@@@ -0,0 +1,24 @@
++{
++ "id": "harness-1",
++ "name": "Example Agent",
++ "provider": "example-ai",
++ "capabilities": {
++ "host_access": true,
++ "mcp": true,
++ "long_autonomy": true,
++ "resume": true,
++ "gpu": true
++ },
++ "address": {
++ "kind": "cli",
++ "target": "example-agent"
++ },
++ "price": {
++ "amount_usd": 0.1,
++ "unit": "hour"
++ },
++ "limits": {
++ "max_duration_seconds": 7200,
++ "max_concurrent_runs": 4
++ }
++}
ddiff --git a/fixtures/platform.harness-offer/valid/capability-vocabulary.json b/fixtures/platform.harness-offer/valid/capability-vocabulary.json
nnew file mode 100644
iindex 00000000..ef510ce5
---- /dev/null
++++ b/fixtures/platform.harness-offer/valid/capability-vocabulary.json
@@@ -0,0 +1,34 @@
++{
++ "id": "harness-2",
++ "name": "Example Desktop Agent",
++ "provider": "example-ai",
++ "capabilities": {
++ "host_access": false,
++ "mcp": true,
++ "long_autonomy": true,
++ "resume": true,
++ "edit": true,
++ "review": true,
++ "tools": true,
++ "vision": true,
++ "browser": true,
++ "computer_use": true,
++ "runtimes": [
++ "code",
++ "browser",
++ "desktop"
++ ]
++ },
++ "address": {
++ "kind": "api",
++ "target": "https://agent.example.com/v1"
++ },
++ "price": {
++ "amount_usd": 0.5,
++ "unit": "hour"
++ },
++ "limits": {
++ "max_duration_seconds": 3600,
++ "max_concurrent_runs": 2
++ }
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 4781665b..defd5d44 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -60658,6 +60658,46 @@
},
"resume": {
"type": "boolean"
++ },
++ "edit": {
++ "type": "boolean",
++ "description": "edits files in its working tree"
++ },
++ "review": {
++ "type": "boolean",
++ "description": "reviews a change and reports findings"
++ },
++ "tools": {
++ "type": "boolean",
++ "description": "calls tools such as a shell, search, or file reads on its own"
++ },
++ "vision": {
++ "type": "boolean",
++ "description": "reads images such as screenshots"
++ },
++ "browser": {
++ "type": "boolean",
++ "description": "drives a web browser"
++ },
++ "computer_use": {
++ "type": "boolean",
++ "description": "operates a desktop through its screen, keyboard, and mouse"
++ },
++ "runtimes": {
++ "type": "array",
++ "uniqueItems": true,
++ "items": {
++ "type": "string",
++ "enum": [
++ "function",
++ "code",
++ "browser",
++ "linux",
++ "desktop",
++ "gpu"
++ ]
++ },
++ "description": "platform.runtime-class@1 classes the harness runs in"
}
},
"required": [
@@@ -61520,7 +61560,7 @@
}
},
"common.resource-summary": {
-- "version": "1.0.0",
++ "version": "1.1.0",
"owner": "contracts",
"status": "active",
"schema": {
@@@ -61552,6 +61592,12 @@
"type": "string",
"pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
},
++ "ovrn": {
++ "type": "string",
++ "maxLength": 200,
++ "pattern": "^ovrn:[a-z][a-z0-9-]{0,31}:prj_[0-9A-HJKMNP-TV-Z]{26}:[a-z][a-z0-9-]{0,39}/[a-z]{3}_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Optional resource name (ADR-034 section 2), the name grants, audit, events, usage records and bills use: ovrn:<service>:<project_id>:<type>/<id>, e.g. ovrn:watch:<project_id>:watch/<wch_id>, with the pattern of common.usage-recorded@1 resource. Its service, project and id are this summary's service, project_id and id. Added in 1.1.0."
++ },
"owner": {
"$ref": "../identity/subject-ref.v1.json"
},
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 844ef5ac..13815090 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -51622,6 +51622,34 @@ export interface HarnessOffer {
mcp: boolean;
long_autonomy: boolean;
resume: boolean;
++ /**
++ * edits files in its working tree
++ */
++ edit?: boolean;
++ /**
++ * reviews a change and reports findings
++ */
++ review?: boolean;
++ /**
++ * calls tools such as a shell, search, or file reads on its own
++ */
++ tools?: boolean;
++ /**
++ * reads images such as screenshots
++ */
++ vision?: boolean;
++ /**
++ * drives a web browser
++ */
++ browser?: boolean;
++ /**
++ * operates a desktop through its screen, keyboard, and mouse
++ */
++ computer_use?: boolean;
++ /**
++ * platform.runtime-class@1 classes the harness runs in
++ */
++ runtimes?: ("function" | "code" | "browser" | "linux" | "desktop" | "gpu")[];
};
address: {
kind: "api" | "cli" | "mcp";
@@@ -51895,7 +51923,7 @@ export interface DeliveryPolicy {
max_payload_bytes?: number;
}
--/** [email protected] (owner: contracts) */
++/** [email protected] (owner: contracts) */
/**
* One resource of any service, as the resource index lists it (roadmap WS-Z7): every service answers GET /api/v1/resources with these, so OpenVibe.Services shows any resource without owning its data.
*/
@@@ -51907,6 +51935,10 @@ export interface ResourceSummary {
kind: string;
service: string;
project_id?: string;
++ /**
++ * Optional resource name (ADR-034 section 2), the name grants, audit, events, usage records and bills use: ovrn:<service>:<project_id>:<type>/<id>, e.g. ovrn:watch:<project_id>:watch/<wch_id>, with the pattern of common.usage-recorded@1 resource. Its service, project and id are this summary's service, project_id and id. Added in 1.1.0.
++ */
++ ovrn?: string;
owner?: SubjectRef;
name?: string;
state: string;
ddiff --git a/lib/ids.js b/lib/ids.js
iindex 42016d7d..eb43b2df 100644
---- a/lib/ids.js
++++ b/lib/ids.js
@@@ -6,7 +6,7 @@
const crypto = require('crypto');
const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
--const PREFIX = { user: 'usr', guest: 'gst', app: 'app', mod: 'mod', agent: 'agt', node: 'nod', event: 'evt', media: 'med', project: 'prj', confirmation: 'cnf' };
++const PREFIX = { user: 'usr', guest: 'gst', app: 'app', mod: 'mod', agent: 'agt', node: 'nod', event: 'evt', media: 'med', project: 'prj', confirmation: 'cnf', watch: 'wch', observation: 'wco', check: 'ckr' };
const SLUG_TYPES = new Set(['service', 'system']);
const ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
const SLUG_RE = /^[a-z][a-z0-9-]{1,39}$/;
ddiff --git a/test/run.js b/test/run.js
iindex 55ef7c21..e79c428f 100644
---- a/test/run.js
++++ b/test/run.js
@@@ -529,6 +529,7 @@ ok(nodeId.startsWith('nod_') && ids.principalSub({ type: 'node', id: nodeId }) =
assert.throws(() => ids.principalSub({ type: 'node', id: 'nod_42' }), /principals/);
ok(!ids.SUBJECT_TYPES.includes('node') && ids.parseSubject(`node:${nodeId}`) === null, 'a node is a principal, never a subject');
assert.throws(() => ids.principalSub({ type: 'user', id: ids.newId('user') }), /principals/);
++for (const [kind, prefix] of [['watch', 'wch'], ['observation', 'wco'], ['check', 'ckr']]) ok(ids.newId(kind).startsWith(`${prefix}_`), `newId(${kind}) is ${prefix}_<ULID>`);
ok(contracts.validate('media.media-ref', { media_id: ids.legacyMediaId('live', 'vod', 42) }).valid, 'legacyMediaId is a valid MediaRef');
ok(contracts.validate('events.event-envelope', { event_id: ids.newId('event'), event_type: 'network.user.created', version: 1, source: 'network', actor: { type: 'system', id: 'network' }, timestamp: new Date().toISOString(), subject: { type: 'user', id: ids.newId('user') }, payload: {} }).valid, 'newId(event) builds a valid envelope');