openvibers/openvibe.contracts Commits
v0.31.0: registry.release-manifest 1.1.0 (ADR-016, Track R): optional components, assets, schema_generation, schema_compatible_from, contract_ranges and metrics_url
Changes
@@@ -4,6 +4,28 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) thfrom `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,the notes were in the tag and commit messages (`git tag -n1`).++## 0.31.0 — 2026-09-23++++Additive: `compat.js` reports no breaking change against v0.30.2.++++- `registry.release-manifest` 1.0.0 → 1.1.0 (ADR-016, roadmap Track R). Six optional fields, so a++ `/release.json` can say what changed and what it still accepts:++ - `components`: `{ <id>: { kind: style|content|script|server, version } }`. A tab applies a release++ in place only when every component that changed is `style`, `content` or `server`. `shell` is the++ catch-all for client code that no other component lists.++ - `assets`: `{ "<logical path>": { url, component, integrity? } }`, the content-addressed URL of each++ asset this release serves.++ - `schema_generation` and `schema_compatible_from` (integers or null): the database schema generation,++ and the oldest generation whose code still runs against it. A rollback below the second is not++ data-safe.++ - `contract_ranges`: `{ <contract id>: { version, accepts: ">=a.b.c <x.y.z", role?: produces|consumes } }`.++ A page from release A works against a server on release B when A's version is in B's `accepts`++ and B's version is in A's `accepts`.++ - `metrics_url`: where tabs POST their update outcome counts (D46).++- A consumer that validates manifests against 0.30.x rejects these fields (`additionalProperties: false`).++ openvibe-shared ≥ 1.5.0 therefore serves only the fields the service's installed openvibe-contracts++ declares. A service starts serving them by moving its pin to this release.++## 0.30.2 — 2026-09-23- New capability `live.follower.read` (Live, internal): `GET /internal/followers`, used by OpenVibe.Network's@@@ -110,7 +110,7 @@},{"id": "registry.release-manifest",-- "version": "1.0.0",++ "version": "1.1.0","owner": "network","schema": "registry/release-manifest.v1.json","visibility": "public",@@@ -2,7 +2,7 @@"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/registry/release-manifest.v1.json","title": "ReleaseManifest",-- "description": "What a running web surface is serving (GET /release.json, ADR-016). Open clients compare it on focus and reconnect; a client outside the supported window is prompted and reloads only when that is safe.",++ "description": "What a running web surface is serving (GET /release.json, ADR-016). Open clients compare it on focus and reconnect. A client outside the supported window is prompted and reloads only when that is safe. Since 1.1.0 the manifest can also say which components changed, so a client can apply style and content changes in place, and which contract versions the server still accepts, so a mixed-version population can be checked.","type": "object","required": ["service", "release", "released_at", "contracts_version", "min_client_release", "mixed_version_window_hours"],"additionalProperties": false,@@@ -18,6 +18,68 @@"additionalProperties": { "type": "string" }},"min_client_release": { "type": ["string", "null"], "description": "Oldest client release still supported; null while every client in the window is." },-- "mixed_version_window_hours": { "type": "integer", "minimum": 0 }++ "mixed_version_window_hours": { "type": "integer", "minimum": 0 },++ "components": {++ "type": "object",++ "description": "Since 1.1.0. Per-component versions. A client applies a new release in place only when every component that changed is a style, content or server component and the contract ranges still match. Any other change is applied by a reload at a safe moment. The shell component covers every client-facing file that no other component lists.",++ "propertyNames": { "pattern": "^[a-z][a-z0-9-]{0,39}$" },++ "additionalProperties": {++ "type": "object",++ "required": ["kind", "version"],++ "additionalProperties": false,++ "properties": {++ "kind": {++ "enum": ["style", "content", "script", "server"],++ "description": "style: stylesheets, swapped in place. content: server-rendered regions (data-ov-content), re-fetched in place. script: code the page runs, applied by a reload. server: code only the server runs, no client action."++ },++ "version": { "type": "string", "pattern": "^[0-9A-Za-z._+-]{1,64}$", "description": "Content hash of the component's files, or a version the service states." }++ }++ }++ },++ "assets": {++ "type": "object",++ "description": "Since 1.1.0. Asset map: each logical URL path the release serves, mapped to its content-addressed URL. HTML from this release references only these URLs.",++ "propertyNames": { "pattern": "^/[^?#\\s]{0,255}$" },++ "additionalProperties": {++ "type": "object",++ "required": ["url", "component"],++ "additionalProperties": false,++ "properties": {++ "url": { "type": "string", "minLength": 1, "maxLength": 512 },++ "component": { "type": "string", "pattern": "^[a-z][a-z0-9-]{0,39}$" },++ "integrity": { "type": "string", "pattern": "^sha(256|384|512)-[A-Za-z0-9+/]+={0,2}$" }++ }++ }++ },++ "schema_generation": {++ "type": ["integer", "null"],++ "minimum": 0,++ "description": "Since 1.1.0. The database schema generation this release migrates to and runs against; null when the service does not track one."++ },++ "schema_compatible_from": {++ "type": ["integer", "null"],++ "minimum": 0,++ "description": "Since 1.1.0. The oldest schema generation whose code still runs against this release's schema, because every migration since then only expanded it. Rolling back to a release whose schema_generation is lower is not data-safe."++ },++ "contract_ranges": {++ "type": "object",++ "description": "Since 1.1.0. For each contract the service produces (its API, as served to its own pages and clients) or consumes: the version this release speaks and the range of versions it accepts from the other side. A page from release A works against a server on release B when, for every contract A produces, A's version is in B's accepts and B's version is in A's accepts.",++ "propertyNames": { "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9_-]+)+$", "maxLength": 100 },++ "additionalProperties": {++ "type": "object",++ "required": ["version", "accepts"],++ "additionalProperties": false,++ "properties": {++ "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },++ "accepts": { "type": "string", "pattern": "^>=\\d+\\.\\d+\\.\\d+ <\\d+\\.\\d+\\.\\d+$", "description": "A half-open range, always written \">=a.b.c <x.y.z\"." },++ "role": { "enum": ["produces", "consumes"], "description": "produces when absent." }++ }++ }++ },++ "metrics_url": {++ "type": ["string", "null"],++ "maxLength": 512,++ "description": "Since 1.1.0. Same-origin URL where clients POST their update outcome counts (applied, reloaded, deferred, failed); null or absent when the service does not collect them."++ }}}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "service": "live",++ "release": "047f2069a1b2",++ "released_at": "2026-09-23T18:02:11Z",++ "contracts_version": "0.31.0",++ "min_client_release": null,++ "mixed_version_window_hours": 24,++ "contract_ranges": {++ "live.web-api": { "version": "1.4.0", "accepts": "^1.0.0" }++ }++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "service": "live",++ "release": "047f2069a1b2",++ "released_at": "2026-09-23T18:02:11Z",++ "contracts_version": "0.31.0",++ "min_client_release": null,++ "mixed_version_window_hours": 24,++ "components": {++ "shell": { "kind": "layout", "version": "c1d2e3f4a5b6" }++ }++}nnew file mode 100644@@@ -0,0 +1,27 @@++{++ "service": "live",++ "release": "047f2069a1b2",++ "released_at": "2026-09-23T18:02:11Z",++ "booted_at": "2026-09-23T18:04:40Z",++ "contracts_version": "0.31.0",++ "packages": { "openvibe-shared": "1.5.0", "openvibe-sdk": "0.4.0" },++ "min_client_release": null,++ "mixed_version_window_hours": 24,++ "components": {++ "styles": { "kind": "style", "version": "3f9a1c07be21" },++ "docs": { "kind": "content", "version": "a07e44d1c9f0" },++ "shell": { "kind": "script", "version": "c1d2e3f4a5b6" },++ "server": { "kind": "server", "version": "047f2069a1b2" }++ },++ "assets": {++ "/css/app.css": { "url": "/css/app.css?v=3f9a1c07be21", "component": "styles", "integrity": "sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" },++ "/js/app.js": { "url": "/js/app.js?v=9b8a7c6d5e4f", "component": "shell" }++ },++ "schema_generation": 42,++ "schema_compatible_from": 40,++ "contract_ranges": {++ "live.web-api": { "version": "1.4.0", "accepts": ">=1.0.0 <2.0.0" },++ "media.object": { "version": "1.0.0", "accepts": ">=1.0.0 <2.0.0", "role": "consumes" }++ },++ "metrics_url": "/release-metrics"++}@@@ -840,14 +840,14 @@}},"registry.release-manifest": {-- "version": "1.0.0",++ "version": "1.1.0","owner": "network","status": "active","schema": {"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/registry/release-manifest.v1.json","title": "ReleaseManifest",-- "description": "What a running web surface is serving (GET /release.json, ADR-016). Open clients compare it on focus and reconnect; a client outside the supported window is prompted and reloads only when that is safe.",++ "description": "What a running web surface is serving (GET /release.json, ADR-016). Open clients compare it on focus and reconnect. A client outside the supported window is prompted and reloads only when that is safe. Since 1.1.0 the manifest can also say which components changed, so a client can apply style and content changes in place, and which contract versions the server still accepts, so a mixed-version population can be checked.","type": "object","required": ["service",@@@ -901,6 +901,125 @@"mixed_version_window_hours": {"type": "integer","minimum": 0++ },++ "components": {++ "type": "object",++ "description": "Since 1.1.0. Per-component versions. A client applies a new release in place only when every component that changed is a style, content or server component and the contract ranges still match. Any other change is applied by a reload at a safe moment. The shell component covers every client-facing file that no other component lists.",++ "propertyNames": {++ "pattern": "^[a-z][a-z0-9-]{0,39}$"++ },++ "additionalProperties": {++ "type": "object",++ "required": [++ "kind",++ "version"++ ],++ "additionalProperties": false,++ "properties": {++ "kind": {++ "enum": [++ "style",++ "content",++ "script",++ "server"++ ],++ "description": "style: stylesheets, swapped in place. content: server-rendered regions (data-ov-content), re-fetched in place. script: code the page runs, applied by a reload. server: code only the server runs, no client action."++ },++ "version": {++ "type": "string",++ "pattern": "^[0-9A-Za-z._+-]{1,64}$",++ "description": "Content hash of the component's files, or a version the service states."++ }++ }++ }++ },++ "assets": {++ "type": "object",++ "description": "Since 1.1.0. Asset map: each logical URL path the release serves, mapped to its content-addressed URL. HTML from this release references only these URLs.",++ "propertyNames": {++ "pattern": "^/[^?#\\s]{0,255}$"++ },++ "additionalProperties": {++ "type": "object",++ "required": [++ "url",++ "component"++ ],++ "additionalProperties": false,++ "properties": {++ "url": {++ "type": "string",++ "minLength": 1,++ "maxLength": 512++ },++ "component": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{0,39}$"++ },++ "integrity": {++ "type": "string",++ "pattern": "^sha(256|384|512)-[A-Za-z0-9+/]+={0,2}$"++ }++ }++ }++ },++ "schema_generation": {++ "type": [++ "integer",++ "null"++ ],++ "minimum": 0,++ "description": "Since 1.1.0. The database schema generation this release migrates to and runs against; null when the service does not track one."++ },++ "schema_compatible_from": {++ "type": [++ "integer",++ "null"++ ],++ "minimum": 0,++ "description": "Since 1.1.0. The oldest schema generation whose code still runs against this release's schema, because every migration since then only expanded it. Rolling back to a release whose schema_generation is lower is not data-safe."++ },++ "contract_ranges": {++ "type": "object",++ "description": "Since 1.1.0. For each contract the service produces (its API, as served to its own pages and clients) or consumes: the version this release speaks and the range of versions it accepts from the other side. A page from release A works against a server on release B when, for every contract A produces, A's version is in B's accepts and B's version is in A's accepts.",++ "propertyNames": {++ "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9_-]+)+$",++ "maxLength": 100++ },++ "additionalProperties": {++ "type": "object",++ "required": [++ "version",++ "accepts"++ ],++ "additionalProperties": false,++ "properties": {++ "version": {++ "type": "string",++ "pattern": "^\\d+\\.\\d+\\.\\d+$"++ },++ "accepts": {++ "type": "string",++ "pattern": "^>=\\d+\\.\\d+\\.\\d+ <\\d+\\.\\d+\\.\\d+$",++ "description": "A half-open range, always written \">=a.b.c <x.y.z\"."++ },++ "role": {++ "enum": [++ "produces",++ "consumes"++ ],++ "description": "produces when absent."++ }++ }++ }++ },++ "metrics_url": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 512,++ "description": "Since 1.1.0. Same-origin URL where clients POST their update outcome counts (applied, reloaded, deferred, failed); null or absent when the service does not collect them."}}}@@@ -266,9 +266,9 @@ export interface ModuleRecord {updated_by?: string;}--/** [email protected] (owner: network) */++/** [email protected] (owner: network) *//**-- * What a running web surface is serving (GET /release.json, ADR-016). Open clients compare it on focus and reconnect; a client outside the supported window is prompted and reloads only when that is safe.++ * What a running web surface is serving (GET /release.json, ADR-016). Open clients compare it on focus and reconnect. A client outside the supported window is prompted and reloads only when that is safe. Since 1.1.0 the manifest can also say which components changed, so a client can apply style and content changes in place, and which contract versions the server still accepts, so a mixed-version population can be checked.*/export interface ReleaseManifest {service: string;@@@ -296,6 +296,65 @@ export interface ReleaseManifest {*/min_client_release: string | null;mixed_version_window_hours: number;++ /**++ * Since 1.1.0. Per-component versions. A client applies a new release in place only when every component that changed is a style, content or server component and the contract ranges still match. Any other change is applied by a reload at a safe moment. The shell component covers every client-facing file that no other component lists.++ */++ components?: {++ [k: string]:++ | {++ /**++ * style: stylesheets, swapped in place. content: server-rendered regions (data-ov-content), re-fetched in place. script: code the page runs, applied by a reload. server: code only the server runs, no client action.++ */++ kind: "style" | "content" | "script" | "server";++ /**++ * Content hash of the component's files, or a version the service states.++ */++ version: string;++ }++ | undefined;++ };++ /**++ * Since 1.1.0. Asset map: each logical URL path the release serves, mapped to its content-addressed URL. HTML from this release references only these URLs.++ */++ assets?: {++ [k: string]:++ | {++ url: string;++ component: string;++ integrity?: string;++ }++ | undefined;++ };++ /**++ * Since 1.1.0. The database schema generation this release migrates to and runs against; null when the service does not track one.++ */++ schema_generation?: number | null;++ /**++ * Since 1.1.0. The oldest schema generation whose code still runs against this release's schema, because every migration since then only expanded it. Rolling back to a release whose schema_generation is lower is not data-safe.++ */++ schema_compatible_from?: number | null;++ /**++ * Since 1.1.0. For each contract the service produces (its API, as served to its own pages and clients) or consumes: the version this release speaks and the range of versions it accepts from the other side. A page from release A works against a server on release B when, for every contract A produces, A's version is in B's accepts and B's version is in A's accepts.++ */++ contract_ranges?: {++ [k: string]:++ | {++ version: string;++ /**++ * A half-open range, always written ">=a.b.c <x.y.z".++ */++ accepts: string;++ /**++ * produces when absent.++ */++ role?: "produces" | "consumes";++ }++ | undefined;++ };++ /**++ * Since 1.1.0. Same-origin URL where clients POST their update outcome counts (applied, reloaded, deferred, failed); null or absent when the service does not collect them.++ */++ metrics_url?: string | null;}/** [email protected] (owner: search) */@@@ -1,12 +1,12 @@{"name": "openvibe-contracts",-- "version": "0.30.2",++ "version": "0.31.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-contracts",-- "version": "0.30.2",++ "version": "0.31.0","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.30.2",++ "version": "0.31.0","description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.","main": "index.js","types": "generated/typescript/index.d.ts",
@@ -4,6 +4,28 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -4,6 +4,28 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
the notes were in the tag and commit messages (`git tag -n1`).
+## 0.31.0 — 2026-09-23
+
+Additive: `compat.js` reports no breaking change against v0.30.2.
+
+- `registry.release-manifest` 1.0.0 → 1.1.0 (ADR-016, roadmap Track R). Six optional fields, so a
+ `/release.json` can say what changed and what it still accepts:
+ - `components`: `{ <id>: { kind: style|content|script|server, version } }`. A tab applies a release
+ in place only when every component that changed is `style`, `content` or `server`. `shell` is the
+ catch-all for client code that no other component lists.
+ - `assets`: `{ "<logical path>": { url, component, integrity? } }`, the content-addressed URL of each
+ asset this release serves.
+ - `schema_generation` and `schema_compatible_from` (integers or null): the database schema generation,
+ and the oldest generation whose code still runs against it. A rollback below the second is not
+ data-safe.
+ - `contract_ranges`: `{ <contract id>: { version, accepts: ">=a.b.c <x.y.z", role?: produces|consumes } }`.
+ A page from release A works against a server on release B when A's version is in B's `accepts`
+ and B's version is in A's `accepts`.
+ - `metrics_url`: where tabs POST their update outcome counts (D46).
+- A consumer that validates manifests against 0.30.x rejects these fields (`additionalProperties: false`).
+ openvibe-shared ≥ 1.5.0 therefore serves only the fields the service's installed openvibe-contracts
+ declares. A service starts serving them by moving its pin to this release.
+
## 0.30.2 — 2026-09-23
## 0.30.2 — 2026-09-23
- New capability `live.follower.read` (Live, internal): `GET /internal/followers`, used by OpenVibe.Network's
- New capability `live.follower.read` (Live, internal): `GET /internal/followers`, used by OpenVibe.Network's
@@ -110,7 +110,7 @@
@@ -110,7 +110,7 @@
},
},
{
{
"id": "registry.release-manifest",
"id": "registry.release-manifest",
- "version": "1.0.0",
+ "version": "1.1.0",
"owner": "network",
"owner": "network",
"schema": "registry/release-manifest.v1.json",
"schema": "registry/release-manifest.v1.json",
"visibility": "public",
"visibility": "public",
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/registry/release-manifest.v1.json",
"$id": "https://openvibe.network/contracts/registry/release-manifest.v1.json",
"title": "ReleaseManifest",
"title": "ReleaseManifest",
- "description": "What a running web surface is serving (GET /release.json, ADR-016). Open clients compare it on focus and reconnect; a client outside the supported window is prompted and reloads only when that is safe.",
+ "description": "What a running web surface is serving (GET /release.json, ADR-016). Open clients compare it on focus and reconnect. A client outside the supported window is prompted and reloads only when that is safe. Since 1.1.0 the manifest can also say which components changed, so a client can apply style and content changes in place, and which contract versions the server still accepts, so a mixed-version population can be checked.",
"type": "object",
"type": "object",
"required": ["service", "release", "released_at", "contracts_version", "min_client_release", "mixed_version_window_hours"],
"required": ["service", "release", "released_at", "contracts_version", "min_client_release", "mixed_version_window_hours"],
"additionalProperties": false,
"additionalProperties": false,
@@ -18,6 +18,68 @@
@@ -18,6 +18,68 @@
"additionalProperties": { "type": "string" }
"additionalProperties": { "type": "string" }
},
},
"min_client_release": { "type": ["string", "null"], "description": "Oldest client release still supported; null while every client in the window is." },
"min_client_release": { "type": ["string", "null"], "description": "Oldest client release still supported; null while every client in the window is." },
- "mixed_version_window_hours": { "type": "integer", "minimum": 0 }
+ "mixed_version_window_hours": { "type": "integer", "minimum": 0 },
+ "components": {
+ "type": "object",
+ "description": "Since 1.1.0. Per-component versions. A client applies a new release in place only when every component that changed is a style, content or server component and the contract ranges still match. Any other change is applied by a reload at a safe moment. The shell component covers every client-facing file that no other component lists.",
+ "propertyNames": { "pattern": "^[a-z][a-z0-9-]{0,39}$" },
+ "additionalProperties": {
+ "type": "object",
+ "required": ["kind", "version"],
+ "additionalProperties": false,
+ "properties": {
+ "kind": {
+ "enum": ["style", "content", "script", "server"],
+ "description": "style: stylesheets, swapped in place. content: server-rendered regions (data-ov-content), re-fetched in place. script: code the page runs, applied by a reload. server: code only the server runs, no client action."
+ },
+ "version": { "type": "string", "pattern": "^[0-9A-Za-z._+-]{1,64}$", "description": "Content hash of the component's files, or a version the service states." }
+ }
+ }
+ },
+ "assets": {
+ "type": "object",
+ "description": "Since 1.1.0. Asset map: each logical URL path the release serves, mapped to its content-addressed URL. HTML from this release references only these URLs.",
+ "propertyNames": { "pattern": "^/[^?#\\s]{0,255}$" },
+ "additionalProperties": {
+ "type": "object",
+ "required": ["url", "component"],
+ "additionalProperties": false,
+ "properties": {
+ "url": { "type": "string", "minLength": 1, "maxLength": 512 },
+ "component": { "type": "string", "pattern": "^[a-z][a-z0-9-]{0,39}$" },
+ "integrity": { "type": "string", "pattern": "^sha(256|384|512)-[A-Za-z0-9+/]+={0,2}$" }
+ }
+ }
+ },
+ "schema_generation": {
+ "type": ["integer", "null"],
+ "minimum": 0,
+ "description": "Since 1.1.0. The database schema generation this release migrates to and runs against; null when the service does not track one."
+ },
+ "schema_compatible_from": {
+ "type": ["integer", "null"],
+ "minimum": 0,
+ "description": "Since 1.1.0. The oldest schema generation whose code still runs against this release's schema, because every migration since then only expanded it. Rolling back to a release whose schema_generation is lower is not data-safe."
+ },
+ "contract_ranges": {
+ "type": "object",
+ "description": "Since 1.1.0. For each contract the service produces (its API, as served to its own pages and clients) or consumes: the version this release speaks and the range of versions it accepts from the other side. A page from release A works against a server on release B when, for every contract A produces, A's version is in B's accepts and B's version is in A's accepts.",
+ "propertyNames": { "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9_-]+)+$", "maxLength": 100 },
+ "additionalProperties": {
+ "type": "object",
+ "required": ["version", "accepts"],
+ "additionalProperties": false,
+ "properties": {
+ "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
+ "accepts": { "type": "string", "pattern": "^>=\\d+\\.\\d+\\.\\d+ <\\d+\\.\\d+\\.\\d+$", "description": "A half-open range, always written \">=a.b.c <x.y.z\"." },
+ "role": { "enum": ["produces", "consumes"], "description": "produces when absent." }
+ }
+ }
+ },
+ "metrics_url": {
+ "type": ["string", "null"],
+ "maxLength": 512,
+ "description": "Since 1.1.0. Same-origin URL where clients POST their update outcome counts (applied, reloaded, deferred, failed); null or absent when the service does not collect them."
+ }
}
}
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "service": "live",
+ "release": "047f2069a1b2",
+ "released_at": "2026-09-23T18:02:11Z",
+ "contracts_version": "0.31.0",
+ "min_client_release": null,
+ "mixed_version_window_hours": 24,
+ "contract_ranges": {
+ "live.web-api": { "version": "1.4.0", "accepts": "^1.0.0" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "service": "live",
+ "release": "047f2069a1b2",
+ "released_at": "2026-09-23T18:02:11Z",
+ "contracts_version": "0.31.0",
+ "min_client_release": null,
+ "mixed_version_window_hours": 24,
+ "components": {
+ "shell": { "kind": "layout", "version": "c1d2e3f4a5b6" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
+{
+ "service": "live",
+ "release": "047f2069a1b2",
+ "released_at": "2026-09-23T18:02:11Z",
+ "booted_at": "2026-09-23T18:04:40Z",
+ "contracts_version": "0.31.0",
+ "packages": { "openvibe-shared": "1.5.0", "openvibe-sdk": "0.4.0" },
+ "min_client_release": null,
+ "mixed_version_window_hours": 24,
+ "components": {
+ "styles": { "kind": "style", "version": "3f9a1c07be21" },
+ "docs": { "kind": "content", "version": "a07e44d1c9f0" },
+ "shell": { "kind": "script", "version": "c1d2e3f4a5b6" },
+ "server": { "kind": "server", "version": "047f2069a1b2" }
+ },
+ "assets": {
+ "/css/app.css": { "url": "/css/app.css?v=3f9a1c07be21", "component": "styles", "integrity": "sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" },
+ "/js/app.js": { "url": "/js/app.js?v=9b8a7c6d5e4f", "component": "shell" }
+ },
+ "schema_generation": 42,
+ "schema_compatible_from": 40,
+ "contract_ranges": {
+ "live.web-api": { "version": "1.4.0", "accepts": ">=1.0.0 <2.0.0" },
+ "media.object": { "version": "1.0.0", "accepts": ">=1.0.0 <2.0.0", "role": "consumes" }
+ },
+ "metrics_url": "/release-metrics"
+}
@@ -840,14 +840,14 @@
@@ -840,14 +840,14 @@
}
}
},
},
"registry.release-manifest": {
"registry.release-manifest": {
- "version": "1.0.0",
+ "version": "1.1.0",
"owner": "network",
"owner": "network",
"status": "active",
"status": "active",
"schema": {
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/registry/release-manifest.v1.json",
"$id": "https://openvibe.network/contracts/registry/release-manifest.v1.json",
"title": "ReleaseManifest",
"title": "ReleaseManifest",
- "description": "What a running web surface is serving (GET /release.json, ADR-016). Open clients compare it on focus and reconnect; a client outside the supported window is prompted and reloads only when that is safe.",
+ "description": "What a running web surface is serving (GET /release.json, ADR-016). Open clients compare it on focus and reconnect. A client outside the supported window is prompted and reloads only when that is safe. Since 1.1.0 the manifest can also say which components changed, so a client can apply style and content changes in place, and which contract versions the server still accepts, so a mixed-version population can be checked.",
"type": "object",
"type": "object",
"required": [
"required": [
"service",
"service",
@@ -901,6 +901,125 @@
@@ -901,6 +901,125 @@
"mixed_version_window_hours": {
"mixed_version_window_hours": {
"type": "integer",
"type": "integer",
"minimum": 0
"minimum": 0
+ },
+ "components": {
+ "type": "object",
+ "description": "Since 1.1.0. Per-component versions. A client applies a new release in place only when every component that changed is a style, content or server component and the contract ranges still match. Any other change is applied by a reload at a safe moment. The shell component covers every client-facing file that no other component lists.",
+ "propertyNames": {
+ "pattern": "^[a-z][a-z0-9-]{0,39}$"
+ },
+ "additionalProperties": {
+ "type": "object",
+ "required": [
+ "kind",
+ "version"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "kind": {
+ "enum": [
+ "style",
+ "content",
+ "script",
+ "server"
+ ],
+ "description": "style: stylesheets, swapped in place. content: server-rendered regions (data-ov-content), re-fetched in place. script: code the page runs, applied by a reload. server: code only the server runs, no client action."
+ },
+ "version": {
+ "type": "string",
+ "pattern": "^[0-9A-Za-z._+-]{1,64}$",
+ "description": "Content hash of the component's files, or a version the service states."
+ }
+ }
+ }
+ },
+ "assets": {
+ "type": "object",
+ "description": "Since 1.1.0. Asset map: each logical URL path the release serves, mapped to its content-addressed URL. HTML from this release references only these URLs.",
+ "propertyNames": {
+ "pattern": "^/[^?#\\s]{0,255}$"
+ },
+ "additionalProperties": {
+ "type": "object",
+ "required": [
+ "url",
+ "component"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "url": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 512
+ },
+ "component": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{0,39}$"
+ },
+ "integrity": {
+ "type": "string",
+ "pattern": "^sha(256|384|512)-[A-Za-z0-9+/]+={0,2}$"
+ }
+ }
+ }
+ },
+ "schema_generation": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "minimum": 0,
+ "description": "Since 1.1.0. The database schema generation this release migrates to and runs against; null when the service does not track one."
+ },
+ "schema_compatible_from": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "minimum": 0,
+ "description": "Since 1.1.0. The oldest schema generation whose code still runs against this release's schema, because every migration since then only expanded it. Rolling back to a release whose schema_generation is lower is not data-safe."
+ },
+ "contract_ranges": {
+ "type": "object",
+ "description": "Since 1.1.0. For each contract the service produces (its API, as served to its own pages and clients) or consumes: the version this release speaks and the range of versions it accepts from the other side. A page from release A works against a server on release B when, for every contract A produces, A's version is in B's accepts and B's version is in A's accepts.",
+ "propertyNames": {
+ "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9_-]+)+$",
+ "maxLength": 100
+ },
+ "additionalProperties": {
+ "type": "object",
+ "required": [
+ "version",
+ "accepts"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "version": {
+ "type": "string",
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
+ },
+ "accepts": {
+ "type": "string",
+ "pattern": "^>=\\d+\\.\\d+\\.\\d+ <\\d+\\.\\d+\\.\\d+$",
+ "description": "A half-open range, always written \">=a.b.c <x.y.z\"."
+ },
+ "role": {
+ "enum": [
+ "produces",
+ "consumes"
+ ],
+ "description": "produces when absent."
+ }
+ }
+ }
+ },
+ "metrics_url": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 512,
+ "description": "Since 1.1.0. Same-origin URL where clients POST their update outcome counts (applied, reloaded, deferred, failed); null or absent when the service does not collect them."
}
}
}
}
}
}
@@ -266,9 +266,9 @@ export interface ModuleRecord {
@@ -266,9 +266,9 @@ export interface ModuleRecord {
updated_by?: string;
updated_by?: string;
}
}
-/** [email protected] (owner: network) */
+/** [email protected] (owner: network) */
/**
/**
- * What a running web surface is serving (GET /release.json, ADR-016). Open clients compare it on focus and reconnect; a client outside the supported window is prompted and reloads only when that is safe.
+ * What a running web surface is serving (GET /release.json, ADR-016). Open clients compare it on focus and reconnect. A client outside the supported window is prompted and reloads only when that is safe. Since 1.1.0 the manifest can also say which components changed, so a client can apply style and content changes in place, and which contract versions the server still accepts, so a mixed-version population can be checked.
*/
*/
export interface ReleaseManifest {
export interface ReleaseManifest {
service: string;
service: string;
@@ -296,6 +296,65 @@ export interface ReleaseManifest {
@@ -296,6 +296,65 @@ export interface ReleaseManifest {
*/
*/
min_client_release: string | null;
min_client_release: string | null;
mixed_version_window_hours: number;
mixed_version_window_hours: number;
+ /**
+ * Since 1.1.0. Per-component versions. A client applies a new release in place only when every component that changed is a style, content or server component and the contract ranges still match. Any other change is applied by a reload at a safe moment. The shell component covers every client-facing file that no other component lists.
+ */
+ components?: {
+ [k: string]:
+ | {
+ /**
+ * style: stylesheets, swapped in place. content: server-rendered regions (data-ov-content), re-fetched in place. script: code the page runs, applied by a reload. server: code only the server runs, no client action.
+ */
+ kind: "style" | "content" | "script" | "server";
+ /**
+ * Content hash of the component's files, or a version the service states.
+ */
+ version: string;
+ }
+ | undefined;
+ };
+ /**
+ * Since 1.1.0. Asset map: each logical URL path the release serves, mapped to its content-addressed URL. HTML from this release references only these URLs.
+ */
+ assets?: {
+ [k: string]:
+ | {
+ url: string;
+ component: string;
+ integrity?: string;
+ }
+ | undefined;
+ };
+ /**
+ * Since 1.1.0. The database schema generation this release migrates to and runs against; null when the service does not track one.
+ */
+ schema_generation?: number | null;
+ /**
+ * Since 1.1.0. The oldest schema generation whose code still runs against this release's schema, because every migration since then only expanded it. Rolling back to a release whose schema_generation is lower is not data-safe.
+ */
+ schema_compatible_from?: number | null;
+ /**
+ * Since 1.1.0. For each contract the service produces (its API, as served to its own pages and clients) or consumes: the version this release speaks and the range of versions it accepts from the other side. A page from release A works against a server on release B when, for every contract A produces, A's version is in B's accepts and B's version is in A's accepts.
+ */
+ contract_ranges?: {
+ [k: string]:
+ | {
+ version: string;
+ /**
+ * A half-open range, always written ">=a.b.c <x.y.z".
+ */
+ accepts: string;
+ /**
+ * produces when absent.
+ */
+ role?: "produces" | "consumes";
+ }
+ | undefined;
+ };
+ /**
+ * Since 1.1.0. Same-origin URL where clients POST their update outcome counts (applied, reloaded, deferred, failed); null or absent when the service does not collect them.
+ */
+ metrics_url?: string | null;
}
}
/** [email protected] (owner: search) */
/** [email protected] (owner: search) */
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.30.2",
+ "version": "0.31.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.30.2",
+ "version": "0.31.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.30.2",
+ "version": "0.31.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",