openvibers/openvibe.contracts Commits
Contracts 0.173.0: OpenVibe.Zone and Media's S3 surface (zones active, access keys, media.object.s3) (#131)
Changes
@@@ -4,6 +4,24 @@ 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.173.0 — 2026-10-14++++- **OpenVibe.Zone is live, and so is Media's S3 surface** (ADR-031 and its zone amendment). Zone owns named object++ zones per project environment at https://openvibe.zone (port 5050), provisioning each change in Media by revision.++ Media serves the zones as S3 buckets and keeps the objects.++- Zone's `zone.*` contracts and capabilities are now active, and two more are added: `zone.resource.read` and++ `zone.resource.control` (delete, for Services).++- **Media has three new capabilities:**++ - `media.object.s3`: the S3 door, reached with an access key;++ - `media.access_key.manage`: issue, list and revoke keys, for a project admin;++ - `media.zone.provision`: Zone only.++- **Access-key schemas:** `media.access-key@1`, `media.access-key-issue-request@1`, `media.access-key-issued@1` (the++ only answer with the secret) and `media.access-key-list@1`.++- **ADR-031 amendment 2026-10-14:**++ - Media issues the keys, not Codes: the verifier must hold the secret, and Codes became the coding harness.++ - The S3 surface is served by Host.++ - Zone serves on its apex until api.openvibe.zone has DNS.++## 0.172.2 — 2026-10-14- **Who consumes `network.subject.merged`, written down.** Live, Chat, Community, Media and Games have folded merged@@@ -5969,7 +5969,7 @@"schema": "zone/object-zone.v1.json","visibility": "public","compatibility": "backward",-- "status": "planned",++ "status": "active","adr": "ADR-031"},{@@@ -5979,7 +5979,7 @@"schema": "zone/object-zone-create-request.v1.json","visibility": "public","compatibility": "backward",-- "status": "planned",++ "status": "active","adr": "ADR-031"},{@@@ -5989,7 +5989,7 @@"schema": "zone/object-zone-update-request.v1.json","visibility": "public","compatibility": "backward",-- "status": "planned",++ "status": "active","adr": "ADR-031"},{@@@ -5999,7 +5999,7 @@"schema": "zone/object-zone-list-query.v1.json","visibility": "public","compatibility": "backward",-- "status": "planned",++ "status": "active","adr": "ADR-031"},{@@@ -6009,7 +6009,7 @@"schema": "zone/object-zone-list.v1.json","visibility": "public","compatibility": "backward",-- "status": "planned",++ "status": "active","adr": "ADR-031"},{@@@ -6019,7 +6019,7 @@"schema": "zone/object-zone-delete-query.v1.json","visibility": "public","compatibility": "backward",-- "status": "planned",++ "status": "active","adr": "ADR-031"},{@@@ -6029,7 +6029,7 @@"schema": "zone/object-zone-usage.v1.json","visibility": "public","compatibility": "backward",-- "status": "planned",++ "status": "active","adr": "ADR-031"},{@@@ -7205,6 +7205,46 @@"compatibility": "backward","status": "active","adr": "Games ADR-0007"++ },++ {++ "id": "media.access-key",++ "version": "1.0.0",++ "owner": "media",++ "schema": "media/access-key.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-031"++ },++ {++ "id": "media.access-key-issue-request",++ "version": "1.0.0",++ "owner": "media",++ "schema": "media/access-key-issue-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-031"++ },++ {++ "id": "media.access-key-issued",++ "version": "1.0.0",++ "owner": "media",++ "schema": "media/access-key-issued.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-031"++ },++ {++ "id": "media.access-key-list",++ "version": "1.0.0",++ "owner": "media",++ "schema": "media/access-key-list.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-031"}]}nnew file mode 100644@@@ -0,0 +1,16 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/media/access-key-issue-request.v1.json",++ "title": "AccessKeyIssueRequest",++ "description": "media.access-key-issue-request@1 (ADR-031): the body of POST /api/v1/projects/:project_id/access-keys on OpenVibe.Media (capability media.access_key.manage), sent by a first-party service (OpenVibe.Zone, OpenVibe.Services) for the person in on_behalf_of, who must be an admin or owner of the project (Network decides). The answer is 201 media.access-key-issued@1. A zone that is not an active zone of this project environment answers 404 media.access_key.unknown_zone; more than 50 live keys in one environment answer 409 media.access_key.limit.",++ "type": "object",++ "additionalProperties": false,++ "required": ["env", "name", "on_behalf_of"],++ "properties": {++ "env": { "enum": ["sandbox", "production"] },++ "name": { "type": "string", "minLength": 1, "maxLength": 80 },++ "permissions": { "enum": ["read", "write", "read-write"], "default": "read-write" },++ "zones": { "type": "array", "maxItems": 20, "uniqueItems": true, "items": { "type": "string", "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$" } },++ "on_behalf_of": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" }++ }++}nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/media/access-key-issued.v1.json",++ "title": "AccessKeyIssued",++ "description": "media.access-key-issued@1 (ADR-031): the 201 of POST /api/v1/projects/:project_id/access-keys. `secret` is the S3 secret access key; it is in this answer only, and Media never shows it again. A caller relays it to the person once and keeps no copy.",++ "type": "object",++ "additionalProperties": false,++ "required": ["key", "secret"],++ "properties": {++ "key": { "$ref": "access-key.v1.json" },++ "secret": { "type": "string", "pattern": "^[A-Za-z0-9_-]{40}$" }++ }++}nnew file mode 100644@@@ -0,0 +1,12 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/media/access-key-list.v1.json",++ "title": "AccessKeyList",++ "description": "media.access-key-list@1 (ADR-031): GET /api/v1/projects/:project_id/access-keys (capability media.access_key.manage, for a project admin in on_behalf_of): every key of the project, newest first, revoked ones included. No secret is ever listed.",++ "type": "object",++ "additionalProperties": false,++ "required": ["keys"],++ "properties": {++ "keys": { "type": "array", "maxItems": 500, "items": { "$ref": "access-key.v1.json" } }++ }++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/media/access-key.v1.json",++ "title": "AccessKey",++ "description": "media.access-key@1 (ADR-031, amended 2026-10-14): one S3 access key of OpenVibe.Media's S3 surface (https://s3.openvibe.media). It is bound to one project environment, reads and/or writes, and may be scoped to some zones of that environment (zone.object-zone@1 ids; empty means every zone). Media issues and verifies it: SigV4 needs the secret, so the party that checks signatures keeps it, encrypted, and shows it once (media.access-key-issued@1). The secret is never part of this record. A revoked key is refused at once; a key scoped only to a zone that is deleted is revoked with it. More fields may be added.",++ "type": "object",++ "additionalProperties": false,++ "required": ["id", "project_id", "env", "name", "permissions", "zones", "created_by", "created_at", "last_used_at", "revoked_at"],++ "properties": {++ "id": { "type": "string", "pattern": "^ovk_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The S3 access key id." },++ "project_id": { "type": "string", "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$" },++ "env": { "enum": ["sandbox", "production"] },++ "name": { "type": "string", "minLength": 1, "maxLength": 80, "description": "What uses the key, as the person who issued it named it." },++ "permissions": { "enum": ["read", "write", "read-write"] },++ "zones": { "type": "array", "maxItems": 20, "uniqueItems": true, "items": { "type": "string", "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$" }, "description": "The zones it may reach; empty = every zone of the environment." },++ "created_by": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" },++ "created_at": { "type": "string", "format": "date-time" },++ "last_used_at": { "type": ["string", "null"], "format": "date-time", "description": "The last signed request, to the minute." },++ "revoked_at": { "type": ["string", "null"], "format": "date-time" }++ }++}@@@ -2,7 +2,7 @@"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone-create-request.v1.json","title": "ObjectZoneCreateRequest",-- "description": "zone.object-zone-create-request@1 (PLANNED; ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.",++ "description": "zone.object-zone-create-request@1 (ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.","type": "object","additionalProperties": false,"required": ["env", "name"],@@@ -2,7 +2,7 @@"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone-delete-query.v1.json","title": "ObjectZoneDeleteQuery",-- "description": "zone.object-zone-delete-query@1 (PLANNED; ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.",++ "description": "zone.object-zone-delete-query@1 (ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.","type": "object","additionalProperties": false,"properties": {@@@ -2,7 +2,7 @@"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone-list-query.v1.json","title": "ObjectZoneListQuery",-- "description": "zone.object-zone-list-query@1 (PLANNED; ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.",++ "description": "zone.object-zone-list-query@1 (ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.","type": "object","additionalProperties": false,"properties": {@@@ -2,7 +2,7 @@"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone-list.v1.json","title": "ObjectZoneList",-- "description": "zone.object-zone-list@1 (PLANNED; ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.",++ "description": "zone.object-zone-list@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.","type": "object","additionalProperties": false,"required": [@@@ -2,7 +2,7 @@"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone-update-request.v1.json","title": "ObjectZoneUpdateRequest",-- "description": "zone.object-zone-update-request@1 (PLANNED; ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.",++ "description": "zone.object-zone-update-request@1 (ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.","type": "object","additionalProperties": false,"minProperties": 1,@@@ -2,7 +2,7 @@"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone-usage.v1.json","title": "ObjectZoneUsage",-- "description": "zone.object-zone-usage@1 (PLANNED; ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.",++ "description": "zone.object-zone-usage@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.","type": "object","additionalProperties": false,"required": [@@@ -2,7 +2,7 @@"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone.v1.json","title": "ObjectZone",-- "description": "zone.object-zone@1 (PLANNED; ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.",++ "description": "zone.object-zone@1 (ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.","type": "object","additionalProperties": false,"required": ["id", "ovrn", "project_id", "env", "name", "default", "access", "state", "media_namespace", "quota_bytes", "revision", "created_at"],@@@ -1,6 +1,6 @@# ADR-031: An S3-compatible surface for OpenVibe.Media--**Status:** Accepted 2026-09-26 (roadmap WS-N task 8). Implementation follows in OpenVibe.Media. Amended 2026-10-02: named object zones (OpenVibe.Zone). A bucket is a named zone inside a project environment, resolved per access key. The environment's own namespace is the zone `default`.++**Status:** Accepted 2026-09-26 (roadmap WS-N task 8). Implemented 2026-10-14 (contracts 0.173.0): Media's S3 surface and OpenVibe.Zone. Amended 2026-10-02: named object zones (OpenVibe.Zone). Amended 2026-10-14: Media issues the access keys. A bucket is a named zone inside a project environment, resolved per access key. The environment's own namespace is the zone `default`.## Context and current evidence@@@ -276,3 +276,24 @@ A hold placed on an object of the zone after that check is still allowed: Media- **Identity:** `contracts.zones.checkObjectZone` refuses a record whose OVRN or namespace names another zone or project. Media refuses such a provision call.- **Usage:** every Media usage rollup carries the zone's resource name as `resource` and its id as `dimension`. Their stored-byte total matches Media's live bytes for the zone.- **Reconciliation:** an injected mismatch between Zone and Media's projection raises the alert.++++## Amendment 2026-10-14: Media issues the access keys; hosts until DNS++++**Status:** Accepted and implemented (contracts 0.173.0, OpenVibe.Media, OpenVibe.Zone).++++**Keys move from Codes to Media.** The Decision above gave key issuance to Codes. Since 2026-10-08 Codes is the coding harness, and the developer console moved to Services, which aggregates authorities and owns none of their records (D33). SigV4 also needs the secret wherever signatures are checked, and that is Media. If Codes kept the keys, every request would carry a secret across services.++- **Media issues, stores and verifies the keys.** `media_access_keys` holds an `ovk_<ULID>` id, the project, environment, permissions (`read`, `write`, `read-write`), an optional zone list, and the secret encrypted with AES-256-GCM under `MEDIA_S3_KEY_SECRET`.++- **The secret is shown once,** in `media.access-key-issued@1`.++- **Keys are managed through Media's API** (capability `media.access_key.manage`). Zone's console and Services call it for a person who administers the project. Media asks Network for that person's role, as its control API does.++- **When a zone is deleted,** a key scoped only to that zone is revoked, and other keys drop the zone from their scope.++++**Hosts.** The S3 surface is served by `Host` (`MEDIA_S3_HOSTS`, `s3.openvibe.media`), not by a path prefix.++- nginx passes the request line untouched, because the client signed that exact path. An nginx `proxy_pass` with a URI part would decode it.++- Zone serves its site and API on its apex, `https://openvibe.zone`.++- `s3.openvibe.media` and `api.openvibe.zone` wait for their DNS records. The DNS token expired, so creating them is the owner's action.++- The certificates already cover both names (wildcards).++++**What v1 leaves out.**++- Multipart uploads and CopyObject answer `NotImplemented`. Single-request objects go up to Media's single-upload limit.++- Overwritten and deleted objects keep counting against a developer project's quota until they are purged. That is Media's existing rule for developer tenants; this ADR's "only live objects count" applies once Media changes that rule.++- Per-zone usage rollups (`media.usage.recorded` with the zone's resource name) are not emitted yet. Zone shows Media's live snapshot.nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "env": "staging",++ "name": "backups",++ "on_behalf_of": "usr_01JZ0000000000000000000A01"++}nnew file mode 100644@@@ -0,0 +1,9 @@++{++ "env": "production",++ "name": "backups",++ "permissions": "write",++ "zones": [++ "zon_01JZ0000000000000000000Z01"++ ],++ "on_behalf_of": "usr_01JZ0000000000000000000A01"++}nnew file mode 100644@@@ -0,0 +1,15 @@++{++ "key": {++ "id": "ovk_01JZ0000000000000000000K01",++ "project_id": "prj_01JZ0000000000000000000P01",++ "env": "production",++ "name": "ci deploys",++ "permissions": "read-write",++ "zones": [],++ "created_by": "usr_01JZ0000000000000000000A01",++ "created_at": "2026-10-14T12:00:00.000Z",++ "last_used_at": null,++ "revoked_at": null++ },++ "secret": "short"++}nnew file mode 100644@@@ -0,0 +1,15 @@++{++ "key": {++ "id": "ovk_01JZ0000000000000000000K01",++ "project_id": "prj_01JZ0000000000000000000P01",++ "env": "production",++ "name": "ci deploys",++ "permissions": "read-write",++ "zones": [],++ "created_by": "usr_01JZ0000000000000000000A01",++ "created_at": "2026-10-14T12:00:00.000Z",++ "last_used_at": null,++ "revoked_at": null++ },++ "secret": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "keys": [++ {++ "id": "ovk_01JZ0000000000000000000K01",++ "secret": "nope"++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,16 @@++{++ "keys": [++ {++ "id": "ovk_01JZ0000000000000000000K01",++ "project_id": "prj_01JZ0000000000000000000P01",++ "env": "production",++ "name": "ci deploys",++ "permissions": "read-write",++ "zones": [],++ "created_by": "usr_01JZ0000000000000000000A01",++ "created_at": "2026-10-14T12:00:00.000Z",++ "last_used_at": null,++ "revoked_at": null++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "id": "ovk_01JZ0000000000000000000K01",++ "project_id": "prj_01JZ0000000000000000000P01",++ "env": "production",++ "name": "ci deploys",++ "permissions": "read-write",++ "zones": [],++ "created_by": "usr_01JZ0000000000000000000A01",++ "created_at": "2026-10-14T12:00:00.000Z",++ "last_used_at": null,++ "revoked_at": null,++ "secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"++}nnew file mode 100644@@@ -0,0 +1,12 @@++{++ "id": "ovk_01JZ0000000000000000000K01",++ "project_id": "prj_01JZ0000000000000000000P01",++ "env": "production",++ "name": "ci deploys",++ "permissions": "read-write",++ "zones": [],++ "created_by": "usr_01JZ0000000000000000000A01",++ "created_at": "2026-10-14T12:00:00.000Z",++ "last_used_at": null,++ "revoked_at": null++}@@@ -68568,12 +68568,12 @@"zone.object-zone": {"version": "1.0.0","owner": "zone",-- "status": "planned",++ "status": "active","schema": {"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone.v1.json","title": "ObjectZone",-- "description": "zone.object-zone@1 (PLANNED; ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.",++ "description": "zone.object-zone@1 (ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.","type": "object","additionalProperties": false,"required": [@@@ -68862,12 +68862,12 @@"zone.object-zone-create-request": {"version": "1.0.0","owner": "zone",-- "status": "planned",++ "status": "active","schema": {"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone-create-request.v1.json","title": "ObjectZoneCreateRequest",-- "description": "zone.object-zone-create-request@1 (PLANNED; ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.",++ "description": "zone.object-zone-create-request@1 (ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.","type": "object","additionalProperties": false,"required": [@@@ -68910,12 +68910,12 @@"zone.object-zone-update-request": {"version": "1.0.0","owner": "zone",-- "status": "planned",++ "status": "active","schema": {"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone-update-request.v1.json","title": "ObjectZoneUpdateRequest",-- "description": "zone.object-zone-update-request@1 (PLANNED; ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.",++ "description": "zone.object-zone-update-request@1 (ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.","type": "object","additionalProperties": false,"minProperties": 1,@@@ -68941,12 +68941,12 @@"zone.object-zone-list-query": {"version": "1.0.0","owner": "zone",-- "status": "planned",++ "status": "active","schema": {"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone-list-query.v1.json","title": "ObjectZoneListQuery",-- "description": "zone.object-zone-list-query@1 (PLANNED; ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.",++ "description": "zone.object-zone-list-query@1 (ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.","type": "object","additionalProperties": false,"properties": {@@@ -68990,12 +68990,12 @@"zone.object-zone-list": {"version": "1.0.0","owner": "zone",-- "status": "planned",++ "status": "active","schema": {"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone-list.v1.json","title": "ObjectZoneList",-- "description": "zone.object-zone-list@1 (PLANNED; ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.",++ "description": "zone.object-zone-list@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.","type": "object","additionalProperties": false,"required": [@@@ -69024,12 +69024,12 @@"zone.object-zone-delete-query": {"version": "1.0.0","owner": "zone",-- "status": "planned",++ "status": "active","schema": {"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone-delete-query.v1.json","title": "ObjectZoneDeleteQuery",-- "description": "zone.object-zone-delete-query@1 (PLANNED; ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.",++ "description": "zone.object-zone-delete-query@1 (ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.","type": "object","additionalProperties": false,"properties": {@@@ -69048,12 +69048,12 @@"zone.object-zone-usage": {"version": "1.0.0","owner": "zone",-- "status": "planned",++ "status": "active","schema": {"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/zone/object-zone-usage.v1.json","title": "ObjectZoneUsage",-- "description": "zone.object-zone-usage@1 (PLANNED; ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.",++ "description": "zone.object-zone-usage@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.","type": "object","additionalProperties": false,"required": [@@@ -80753,6 +80753,197 @@}}}++ },++ "media.access-key": {++ "version": "1.0.0",++ "owner": "media",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/media/access-key.v1.json",++ "title": "AccessKey",++ "description": "media.access-key@1 (ADR-031, amended 2026-10-14): one S3 access key of OpenVibe.Media's S3 surface (https://s3.openvibe.media). It is bound to one project environment, reads and/or writes, and may be scoped to some zones of that environment (zone.object-zone@1 ids; empty means every zone). Media issues and verifies it: SigV4 needs the secret, so the party that checks signatures keeps it, encrypted, and shows it once (media.access-key-issued@1). The secret is never part of this record. A revoked key is refused at once; a key scoped only to a zone that is deleted is revoked with it. More fields may be added.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "project_id",++ "env",++ "name",++ "permissions",++ "zones",++ "created_by",++ "created_at",++ "last_used_at",++ "revoked_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^ovk_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The S3 access key id."++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "env": {++ "enum": [++ "sandbox",++ "production"++ ]++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80,++ "description": "What uses the key, as the person who issued it named it."++ },++ "permissions": {++ "enum": [++ "read",++ "write",++ "read-write"++ ]++ },++ "zones": {++ "type": "array",++ "maxItems": 20,++ "uniqueItems": true,++ "items": {++ "type": "string",++ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "description": "The zones it may reach; empty = every zone of the environment."++ },++ "created_by": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "last_used_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "The last signed request, to the minute."++ },++ "revoked_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ }++ }++ }++ },++ "media.access-key-issue-request": {++ "version": "1.0.0",++ "owner": "media",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/media/access-key-issue-request.v1.json",++ "title": "AccessKeyIssueRequest",++ "description": "media.access-key-issue-request@1 (ADR-031): the body of POST /api/v1/projects/:project_id/access-keys on OpenVibe.Media (capability media.access_key.manage), sent by a first-party service (OpenVibe.Zone, OpenVibe.Services) for the person in on_behalf_of, who must be an admin or owner of the project (Network decides). The answer is 201 media.access-key-issued@1. A zone that is not an active zone of this project environment answers 404 media.access_key.unknown_zone; more than 50 live keys in one environment answer 409 media.access_key.limit.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "env",++ "name",++ "on_behalf_of"++ ],++ "properties": {++ "env": {++ "enum": [++ "sandbox",++ "production"++ ]++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ },++ "permissions": {++ "enum": [++ "read",++ "write",++ "read-write"++ ],++ "default": "read-write"++ },++ "zones": {++ "type": "array",++ "maxItems": 20,++ "uniqueItems": true,++ "items": {++ "type": "string",++ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ },++ "on_behalf_of": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++ }++ },++ "media.access-key-issued": {++ "version": "1.0.0",++ "owner": "media",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/media/access-key-issued.v1.json",++ "title": "AccessKeyIssued",++ "description": "media.access-key-issued@1 (ADR-031): the 201 of POST /api/v1/projects/:project_id/access-keys. `secret` is the S3 secret access key; it is in this answer only, and Media never shows it again. A caller relays it to the person once and keeps no copy.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "key",++ "secret"++ ],++ "properties": {++ "key": {++ "$ref": "access-key.v1.json"++ },++ "secret": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_-]{40}$"++ }++ }++ }++ },++ "media.access-key-list": {++ "version": "1.0.0",++ "owner": "media",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/media/access-key-list.v1.json",++ "title": "AccessKeyList",++ "description": "media.access-key-list@1 (ADR-031): GET /api/v1/projects/:project_id/access-keys (capability media.access_key.manage, for a project admin in on_behalf_of): every key of the project, newest first, revoked ones included. No secret is ever listed.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "keys"++ ],++ "properties": {++ "keys": {++ "type": "array",++ "maxItems": 500,++ "items": {++ "$ref": "access-key.v1.json"++ }++ }++ }++ }}}}@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Actor API",-- "version": "0.172.2",-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.Chat API",-- "version": "0.172.2",-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.173.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"@@@ -117,8 +117,8 @@"service": "media","name": "OpenVibe.Media","origin": "https://openvibe.media",-- "operations": 10,-- "capabilities": 7++ "operations": 15,++ "capabilities": 10},{"service": "media-hub",@@@ -245,6 +245,13 @@"origin": "https://openvibe.work","operations": 5,"capabilities": 1++ },++ {++ "service": "zone",++ "name": "OpenVibe.Zone",++ "origin": "https://openvibe.zone",++ "operations": 9,++ "capabilities": 8}]}@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Inventory API",-- "version": "0.172.2",-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.173.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"@@@ -146,6 +146,310 @@}}},++ "/api/v1/projects/{project_id}/access-keys": {++ "get": {++ "operationId": "get_api_v1_projects_project_id_access_keys",++ "summary": "Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies).",++ "description": "**media.access_key.manage** (first-party): Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies). A first-party service (OpenVibe.Zone, OpenVibe.Services) calls for the person in on_behalf_of, who must be an admin or owner of the project (Media asks Network, network.project.member.read). POST /api/v1/projects/:project_id/access-keys takes media.access-key-issue-request@1 and answers 201 media.access-key-issued@1, the only answer that carries the secret; GET lists media.access-key-list@1; DELETE /:key_id revokes at once. Loopback only.",++ "tags": [++ "media.access_key"++ ],++ "x-openvibe-capabilities": [++ "media.access_key.manage"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "internal-write",++ "security": [++ {++ "openvibeToken": [++ "media.access_key.manage"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/media.access-key-issue-request.v1"++ },++ "parameters": [++ {++ "name": "project_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "env",++ "in": "query",++ "required": true,++ "schema": {++ "enum": [++ "sandbox",++ "production"++ ]++ }++ },++ {++ "name": "name",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ }++ },++ {++ "name": "permissions",++ "in": "query",++ "required": false,++ "schema": {++ "enum": [++ "read",++ "write",++ "read-write"++ ],++ "default": "read-write"++ }++ },++ {++ "name": "zones",++ "in": "query",++ "required": false,++ "schema": {++ "type": "array",++ "maxItems": 20,++ "uniqueItems": true,++ "items": {++ "type": "string",++ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++ },++ {++ "name": "on_behalf_of",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/media.access-key-issued.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ },++ "post": {++ "operationId": "post_api_v1_projects_project_id_access_keys",++ "summary": "Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies).",++ "description": "**media.access_key.manage** (first-party): Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies). A first-party service (OpenVibe.Zone, OpenVibe.Services) calls for the person in on_behalf_of, who must be an admin or owner of the project (Media asks Network, network.project.member.read). POST /api/v1/projects/:project_id/access-keys takes media.access-key-issue-request@1 and answers 201 media.access-key-issued@1, the only answer that carries the secret; GET lists media.access-key-list@1; DELETE /:key_id revokes at once. Loopback only.",++ "tags": [++ "media.access_key"++ ],++ "x-openvibe-capabilities": [++ "media.access_key.manage"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "internal-write",++ "security": [++ {++ "openvibeToken": [++ "media.access_key.manage"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/media.access-key-issue-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "project_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/media.access-key-issued.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/projects/{project_id}/access-keys/{key_id}": {++ "delete": {++ "operationId": "delete_api_v1_projects_project_id_access_keys_key_id",++ "summary": "Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies).",++ "description": "**media.access_key.manage** (first-party): Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies). A first-party service (OpenVibe.Zone, OpenVibe.Services) calls for the person in on_behalf_of, who must be an admin or owner of the project (Media asks Network, network.project.member.read). POST /api/v1/projects/:project_id/access-keys takes media.access-key-issue-request@1 and answers 201 media.access-key-issued@1, the only answer that carries the secret; GET lists media.access-key-list@1; DELETE /:key_id revokes at once. Loopback only.",++ "tags": [++ "media.access_key"++ ],++ "x-openvibe-capabilities": [++ "media.access_key.manage"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "internal-write",++ "security": [++ {++ "openvibeToken": [++ "media.access_key.manage"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/media.access-key-issue-request.v1"++ },++ "parameters": [++ {++ "name": "project_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "key_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "env",++ "in": "query",++ "required": true,++ "schema": {++ "enum": [++ "sandbox",++ "production"++ ]++ }++ },++ {++ "name": "name",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ }++ },++ {++ "name": "permissions",++ "in": "query",++ "required": false,++ "schema": {++ "enum": [++ "read",++ "write",++ "read-write"++ ],++ "default": "read-write"++ }++ },++ {++ "name": "zones",++ "in": "query",++ "required": false,++ "schema": {++ "type": "array",++ "maxItems": 20,++ "uniqueItems": true,++ "items": {++ "type": "string",++ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++ },++ {++ "name": "on_behalf_of",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/media.access-key-issued.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",@@@ -599,50 +903,391 @@}}}-- }-- },-- "components": {-- "schemas": {-- "common.resource-control-request.v1": {-- "title": "ResourceControlRequest",-- "description": "common.resource-control-request@1: one control operation OpenVibe.Services (or Actor's Console) sends to the authority that owns a resource (ADR-048, plan T13). Services never mutates another service's database; every create, update, start, stop, delete, resize, rotate, pair, grant or revoke is this request to the owning authority's control API, which decides and answers with a common.resource-control-result@1. A request names exactly one of resource (every action but create) or resource_kind (create, before the name exists). project_id is the tenancy boundary and equals the project segment of resource. idempotency_key makes a retry safe: the authority answers a repeated key with the first result. A sensitive action (money, publishing, deleting, physical control) is refused with confirmation_required until its owner approves; the retry carries that confirmation_id (roadmap WS-Z2). The cross-field rules are checked by contracts.resources.checkControlRequest.",-- "type": "object",-- "additionalProperties": false,-- "required": [-- "action",-- "project_id",-- "idempotency_key"++ },++ "/internal/zones/{zone_id}": {++ "get": {++ "operationId": "get_internal_zones_zone_id",++ "summary": "Provision a zone in OpenVibe.Media (ADR-031 amendment 2026-10-02), granted only to OpenVibe.Zone: PUT /internal/zones/:zone_id with the zone.object-zone@1 record Zone owns.",++ "description": "**media.zone.provision** (first-party): Provision a zone in OpenVibe.Media (ADR-031 amendment 2026-10-02), granted only to OpenVibe.Zone: PUT /internal/zones/:zone_id with the zone.object-zone@1 record Zone owns. Media applies a record only when its revision is higher than the one it holds, checks it with contracts.zones.checkObjectZone, and refuses a name another zone of the environment holds (409 media.zone.name_retired) and any change of project, environment, name or namespace (409 media.zone.immutable). Media keeps a projection of each zone to resolve S3 bucket names, enforce the zone's quota_bytes beside the environment's, and answer NoSuchBucket for a zone that is deleting or deleted. GET /internal/zones/:zone_id reports the live objects and, while the zone is deleting, the objects remaining and held; Media tombstones and purges a deleting zone's objects (no restore window) and records when none remain. Loopback only.",++ "tags": [++ "media.zone"],-- "oneOf": [-- {-- "required": [-- "resource"-- ]-- },++ "x-openvibe-capabilities": [++ "media.zone.provision"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "internal-write",++ "security": [{-- "required": [-- "resource_kind"++ "openvibeToken": [++ "media.zone.provision"]}],-- "properties": {-- "action": {-- "enum": [-- "create",-- "update",-- "delete",-- "start",-- "stop",-- "suspend",-- "resume",-- "resize",-- "rotate",-- "pair",-- "grant",-- "revoke",-- "archive"-- ]-- },++ "x-openvibe-input": {++ "$ref": "#/components/schemas/zone.object-zone.v1"++ },++ "parameters": [++ {++ "name": "zone_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "id",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ },++ {++ "name": "ovrn",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "pattern": "^ovrn:zone:prj_[0-9A-HJKMNP-TV-Z]{26}:object-zone/zon_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The resource name used in grants, audit, events, usage records and bills (ADR-034 section 2)."++ },++ "description": "The resource name used in grants, audit, events, usage records and bills (ADR-034 section 2)."++ },++ {++ "name": "project_id",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ },++ {++ "name": "env",++ "in": "query",++ "required": true,++ "schema": {++ "enum": [++ "sandbox",++ "production"++ ],++ "description": "Fixed at creation. A sandbox key never resolves a production zone."++ },++ "description": "Fixed at creation. A sandbox key never resolves a production zone."++ },++ {++ "name": "name",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "minLength": 3,++ "maxLength": 63,++ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",++ "description": "The S3 bucket name. It is unique among all the zones of one project environment, deleted ones included: a deleted zone's name is retired, never given to another zone. It is valid as an S3 bucket name."++ },++ "description": "The S3 bucket name. It is unique among all the zones of one project environment, deleted ones included: a deleted zone's name is retired, never given to another zone. It is valid as an S3 bucket name."++ },++ {++ "name": "default",++ "in": "query",++ "required": true,++ "schema": {++ "type": "boolean",++ "description": "The environment's default zone, named `default`. Its media_namespace is the environment root."++ },++ "description": "The environment's default zone, named `default`. Its media_namespace is the environment root."++ },++ {++ "name": "description",++ "in": "query",++ "required": false,++ "schema": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 200++ }++ },++ {++ "name": "access",++ "in": "query",++ "required": true,++ "schema": {++ "enum": [++ "private",++ "per-object"++ ],++ "description": "private: every named zone in v1. Its objects never have a public_url and never enter a public cache. per-object: only the default zone, whose existing objects keep their own visibility (public, unlisted or private), public URLs and cached copies, as media.object@1 allows. The S3 surface never serves an object without a signature, in either kind of zone. Publication of a named zone would add a value in a later minor version."++ },++ "description": "private: every named zone in v1. Its objects never have a public_url and never enter a public cache. per-object: only the default zone, whose existing objects keep their own visibility (public, unlisted or private), public URLs and cached copies, as media.object@1 allows. The S3 surface never serves an object without a signature, in either kind of zone. Publication of a named zone would add a value in a later minor version."++ },++ {++ "name": "state",++ "in": "query",++ "required": true,++ "schema": {++ "enum": [++ "provisioning",++ "active",++ "deleting",++ "deleted"++ ],++ "description": "provisioning: Media has not yet acknowledged the zone. active: it serves requests. deleting: requests answer NoSuchBucket while Media tombstones the objects and erases every copy. deleted: no object or copy remains. The record is kept, and its name stays retired."++ },++ "description": "provisioning: Media has not yet acknowledged the zone. active: it serves requests. deleting: requests answer NoSuchBucket while Media tombstones the objects and erases every copy. deleted: no object or copy remains. The record is kept, and its name stays retired."++ },++ {++ "name": "media_namespace",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}(\\.sandbox)?(\\.zon_[0-9A-HJKMNP-TV-Z]{26})?$",++ "description": "Where Media keeps the zone's objects. It is the environment root for the default zone, and <root>.<id> for every other zone."++ },++ "description": "Where Media keeps the zone's objects. It is the environment root for the default zone, and <root>.<id> for every other zone."++ },++ {++ "name": "quota_bytes",++ "in": "query",++ "required": true,++ "schema": {++ "type": [++ "integer",++ "null"++ ],++ "minimum": 1,++ "description": "An optional byte ceiling for this zone. Media enforces it together with the project environment's byte quota, and a write over either is refused with QuotaExceeded. null means only the environment quota applies."++ },++ "description": "An optional byte ceiling for this zone. Media enforces it together with the project environment's byte quota, and a write over either is refused with QuotaExceeded. null means only the environment quota applies."++ },++ {++ "name": "usage",++ "in": "query",++ "required": false,++ "schema": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "stored_bytes",++ "objects",++ "as_of"++ ],++ "description": "A snapshot of the live objects, as Media reports it. Zone never counts. Billed usage comes from Media's hourly media.usage.recorded rollups, whose dimension is this id in lowercase.",++ "properties": {++ "stored_bytes": {++ "type": "integer",++ "minimum": 0++ },++ "objects": {++ "type": "integer",++ "minimum": 0++ },++ "as_of": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "description": "A snapshot of the live objects, as Media reports it. Zone never counts. Billed usage comes from Media's hourly media.usage.recorded rollups, whose dimension is this id in lowercase."++ },++ {++ "name": "deletion",++ "in": "query",++ "required": false,++ "schema": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "requested_at",++ "objects_remaining",++ "held_objects",++ "completed_at"++ ],++ "description": "Progress of a zone deletion, as Media reports it.",++ "properties": {++ "requested_at": {++ "type": "string",++ "format": "date-time"++ },++ "objects_remaining": {++ "type": "integer",++ "minimum": 0,++ "description": "Objects not yet tombstoned, plus tombstoned objects with a copy not yet confirmed erased."++ },++ "held_objects": {++ "type": "integer",++ "minimum": 0,++ "description": "Objects whose erasure waits for a retention hold (ADR-006). They are already unreadable."++ },++ "completed_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ }++ }++ },++ "description": "Progress of a zone deletion, as Media reports it."++ },++ {++ "name": "revision",++ "in": "query",++ "required": true,++ "schema": {++ "type": "integer",++ "minimum": 1,++ "description": "Raised by every change. Zone sends it when it provisions the change in Media, and Media applies only a higher revision."++ },++ "description": "Raised by every change. Zone sends it when it provisions the change in Media, and Media applies only a higher revision."++ },++ {++ "name": "created_at",++ "in": "query",++ "required": true,++ "schema": {++ "type": "string",++ "format": "date-time"++ }++ },++ {++ "name": "updated_at",++ "in": "query",++ "required": false,++ "schema": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success"++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ },++ "put": {++ "operationId": "put_internal_zones_zone_id",++ "summary": "Provision a zone in OpenVibe.Media (ADR-031 amendment 2026-10-02), granted only to OpenVibe.Zone: PUT /internal/zones/:zone_id with the zone.object-zone@1 record Zone owns.",++ "description": "**media.zone.provision** (first-party): Provision a zone in OpenVibe.Media (ADR-031 amendment 2026-10-02), granted only to OpenVibe.Zone: PUT /internal/zones/:zone_id with the zone.object-zone@1 record Zone owns. Media applies a record only when its revision is higher than the one it holds, checks it with contracts.zones.checkObjectZone, and refuses a name another zone of the environment holds (409 media.zone.name_retired) and any change of project, environment, name or namespace (409 media.zone.immutable). Media keeps a projection of each zone to resolve S3 bucket names, enforce the zone's quota_bytes beside the environment's, and answer NoSuchBucket for a zone that is deleting or deleted. GET /internal/zones/:zone_id reports the live objects and, while the zone is deleting, the objects remaining and held; Media tombstones and purges a deleting zone's objects (no restore window) and records when none remain. Loopback only.",++ "tags": [++ "media.zone"++ ],++ "x-openvibe-capabilities": [++ "media.zone.provision"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "internal-write",++ "security": [++ {++ "openvibeToken": [++ "media.zone.provision"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/zone.object-zone.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "zone_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success"++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ }++ },++ "components": {++ "schemas": {++ "common.resource-control-request.v1": {++ "title": "ResourceControlRequest",++ "description": "common.resource-control-request@1: one control operation OpenVibe.Services (or Actor's Console) sends to the authority that owns a resource (ADR-048, plan T13). Services never mutates another service's database; every create, update, start, stop, delete, resize, rotate, pair, grant or revoke is this request to the owning authority's control API, which decides and answers with a common.resource-control-result@1. A request names exactly one of resource (every action but create) or resource_kind (create, before the name exists). project_id is the tenancy boundary and equals the project segment of resource. idempotency_key makes a retry safe: the authority answers a repeated key with the first result. A sensitive action (money, publishing, deleting, physical control) is refused with confirmation_required until its owner approves; the retry carries that confirmation_id (roadmap WS-Z2). The cross-field rules are checked by contracts.resources.checkControlRequest.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "action",++ "project_id",++ "idempotency_key"++ ],++ "oneOf": [++ {++ "required": [++ "resource"++ ]++ },++ {++ "required": [++ "resource_kind"++ ]++ }++ ],++ "properties": {++ "action": {++ "enum": [++ "create",++ "update",++ "delete",++ "start",++ "stop",++ "suspend",++ "resume",++ "resize",++ "rotate",++ "pair",++ "grant",++ "revoke",++ "archive"++ ]++ },"resource": {"$ref": "#/components/schemas/common.resource-name.v1","description": "The resource acted on; required for every action but create."@@@ -1062,6 +1707,151 @@}]},++ "media.access-key-issue-request.v1": {++ "title": "AccessKeyIssueRequest",++ "description": "media.access-key-issue-request@1 (ADR-031): the body of POST /api/v1/projects/:project_id/access-keys on OpenVibe.Media (capability media.access_key.manage), sent by a first-party service (OpenVibe.Zone, OpenVibe.Services) for the person in on_behalf_of, who must be an admin or owner of the project (Network decides). The answer is 201 media.access-key-issued@1. A zone that is not an active zone of this project environment answers 404 media.access_key.unknown_zone; more than 50 live keys in one environment answer 409 media.access_key.limit.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "env",++ "name",++ "on_behalf_of"++ ],++ "properties": {++ "env": {++ "enum": [++ "sandbox",++ "production"++ ]++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80++ },++ "permissions": {++ "enum": [++ "read",++ "write",++ "read-write"++ ],++ "default": "read-write"++ },++ "zones": {++ "type": "array",++ "maxItems": 20,++ "uniqueItems": true,++ "items": {++ "type": "string",++ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ },++ "on_behalf_of": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++ },++ "media.access-key-issued.v1": {++ "title": "AccessKeyIssued",++ "description": "media.access-key-issued@1 (ADR-031): the 201 of POST /api/v1/projects/:project_id/access-keys. `secret` is the S3 secret access key; it is in this answer only, and Media never shows it again. A caller relays it to the person once and keeps no copy.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "key",++ "secret"++ ],++ "properties": {++ "key": {++ "$ref": "#/components/schemas/media.access-key.v1"++ },++ "secret": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_-]{40}$"++ }++ }++ },++ "media.access-key.v1": {++ "title": "AccessKey",++ "description": "media.access-key@1 (ADR-031, amended 2026-10-14): one S3 access key of OpenVibe.Media's S3 surface (https://s3.openvibe.media). It is bound to one project environment, reads and/or writes, and may be scoped to some zones of that environment (zone.object-zone@1 ids; empty means every zone). Media issues and verifies it: SigV4 needs the secret, so the party that checks signatures keeps it, encrypted, and shows it once (media.access-key-issued@1). The secret is never part of this record. A revoked key is refused at once; a key scoped only to a zone that is deleted is revoked with it. More fields may be added.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "project_id",++ "env",++ "name",++ "permissions",++ "zones",++ "created_by",++ "created_at",++ "last_used_at",++ "revoked_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^ovk_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The S3 access key id."++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "env": {++ "enum": [++ "sandbox",++ "production"++ ]++ },++ "name": {++ "type": "string",++ "minLength": 1,++ "maxLength": 80,++ "description": "What uses the key, as the person who issued it named it."++ },++ "permissions": {++ "enum": [++ "read",++ "write",++ "read-write"++ ]++ },++ "zones": {++ "type": "array",++ "maxItems": 20,++ "uniqueItems": true,++ "items": {++ "type": "string",++ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "description": "The zones it may reach; empty = every zone of the environment."++ },++ "created_by": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "last_used_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "The last signed request, to the minute."++ },++ "revoked_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ }++ }++ },"media.avatar-ingest-request.v1": {"title": "MediaAvatarIngestRequest","description": "media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).",@@@ -1476,6 +2266,293 @@]}}++ },++ "zone.object-zone.v1": {++ "title": "ObjectZone",++ "description": "zone.object-zone@1 (ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "ovrn",++ "project_id",++ "env",++ "name",++ "default",++ "access",++ "state",++ "media_namespace",++ "quota_bytes",++ "revision",++ "created_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "ovrn": {++ "type": "string",++ "pattern": "^ovrn:zone:prj_[0-9A-HJKMNP-TV-Z]{26}:object-zone/zon_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The resource name used in grants, audit, events, usage records and bills (ADR-034 section 2)."++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "env": {++ "enum": [++ "sandbox",++ "production"++ ],++ "description": "Fixed at creation. A sandbox key never resolves a production zone."++ },++ "name": {++ "type": "string",++ "minLength": 3,++ "maxLength": 63,++ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",++ "description": "The S3 bucket name. It is unique among all the zones of one project environment, deleted ones included: a deleted zone's name is retired, never given to another zone. It is valid as an S3 bucket name."++ },++ "default": {++ "type": "boolean",++ "description": "The environment's default zone, named `default`. Its media_namespace is the environment root."++ },++ "description": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 200++ },++ "access": {++ "enum": [++ "private",++ "per-object"++ ],++ "description": "private: every named zone in v1. Its objects never have a public_url and never enter a public cache. per-object: only the default zone, whose existing objects keep their own visibility (public, unlisted or private), public URLs and cached copies, as media.object@1 allows. The S3 surface never serves an object without a signature, in either kind of zone. Publication of a named zone would add a value in a later minor version."++ },++ "state": {++ "enum": [++ "provisioning",++ "active",++ "deleting",++ "deleted"++ ],++ "description": "provisioning: Media has not yet acknowledged the zone. active: it serves requests. deleting: requests answer NoSuchBucket while Media tombstones the objects and erases every copy. deleted: no object or copy remains. The record is kept, and its name stays retired."++ },++ "media_namespace": {++ "type": "string",++ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}(\\.sandbox)?(\\.zon_[0-9A-HJKMNP-TV-Z]{26})?$",++ "description": "Where Media keeps the zone's objects. It is the environment root for the default zone, and <root>.<id> for every other zone."++ },++ "quota_bytes": {++ "type": [++ "integer",++ "null"++ ],++ "minimum": 1,++ "description": "An optional byte ceiling for this zone. Media enforces it together with the project environment's byte quota, and a write over either is refused with QuotaExceeded. null means only the environment quota applies."++ },++ "usage": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "stored_bytes",++ "objects",++ "as_of"++ ],++ "description": "A snapshot of the live objects, as Media reports it. Zone never counts. Billed usage comes from Media's hourly media.usage.recorded rollups, whose dimension is this id in lowercase.",++ "properties": {++ "stored_bytes": {++ "type": "integer",++ "minimum": 0++ },++ "objects": {++ "type": "integer",++ "minimum": 0++ },++ "as_of": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "deletion": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "requested_at",++ "objects_remaining",++ "held_objects",++ "completed_at"++ ],++ "description": "Progress of a zone deletion, as Media reports it.",++ "properties": {++ "requested_at": {++ "type": "string",++ "format": "date-time"++ },++ "objects_remaining": {++ "type": "integer",++ "minimum": 0,++ "description": "Objects not yet tombstoned, plus tombstoned objects with a copy not yet confirmed erased."++ },++ "held_objects": {++ "type": "integer",++ "minimum": 0,++ "description": "Objects whose erasure waits for a retention hold (ADR-006). They are already unreadable."++ },++ "completed_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ }++ }++ },++ "revision": {++ "type": "integer",++ "minimum": 1,++ "description": "Raised by every change. Zone sends it when it provisions the change in Media, and Media applies only a higher revision."++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "updated_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ }++ },++ "allOf": [++ {++ "if": {++ "properties": {++ "default": {++ "const": true++ }++ },++ "required": [++ "default"++ ]++ },++ "then": {++ "properties": {++ "name": {++ "const": "default"++ },++ "access": {++ "const": "per-object"++ },++ "media_namespace": {++ "type": "string",++ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}(\\.sandbox)?$"++ }++ }++ },++ "else": {++ "properties": {++ "name": {++ "not": {++ "const": "default"++ }++ },++ "access": {++ "const": "private"++ },++ "media_namespace": {++ "type": "string",++ "pattern": "\\.zon_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++ }++ },++ {++ "if": {++ "properties": {++ "env": {++ "const": "sandbox"++ }++ },++ "required": [++ "env"++ ]++ },++ "then": {++ "properties": {++ "media_namespace": {++ "type": "string",++ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}\\.sandbox(\\.|$)"++ }++ }++ },++ "else": {++ "properties": {++ "media_namespace": {++ "type": "string",++ "not": {++ "pattern": "\\.sandbox(\\.|$)"++ }++ }++ }++ }++ },++ {++ "if": {++ "properties": {++ "state": {++ "enum": [++ "deleting",++ "deleted"++ ]++ }++ },++ "required": [++ "state"++ ]++ },++ "then": {++ "required": [++ "deletion"++ ]++ }++ },++ {++ "if": {++ "properties": {++ "state": {++ "const": "deleted"++ }++ },++ "required": [++ "state"++ ]++ },++ "then": {++ "properties": {++ "deletion": {++ "type": "object",++ "properties": {++ "objects_remaining": {++ "const": 0++ },++ "held_objects": {++ "const": 0++ },++ "completed_at": {++ "type": "string"++ }++ }++ }++ }++ }++ }++ ]}},"securitySchemes": {@@@ -1486,5 +2563,39 @@"description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."}}-- }++ },++ "x-openvibe-other-bindings": [++ {++ "capability": "media.object.s3",++ "binding": "GET s3:/"++ },++ {++ "capability": "media.object.s3",++ "binding": "HEAD s3:/:zone"++ },++ {++ "capability": "media.object.s3",++ "binding": "GET s3:/:zone"++ },++ {++ "capability": "media.object.s3",++ "binding": "POST s3:/:zone?delete"++ },++ {++ "capability": "media.object.s3",++ "binding": "PUT s3:/:zone/:key"++ },++ {++ "capability": "media.object.s3",++ "binding": "GET s3:/:zone/:key"++ },++ {++ "capability": "media.object.s3",++ "binding": "HEAD s3:/:zone/:key"++ },++ {++ "capability": "media.object.s3",++ "binding": "DELETE s3:/:zone/:key"++ }++ ]}@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Network API",-- "version": "0.172.2",-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.News API",-- "version": "0.172.2",-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.173.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"nnew file mode 100644@@@ -0,0 +1,1602 @@++{++ "openapi": "3.1.0",++ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",++ "info": {++ "title": "OpenVibe.Zone API",++ "version": "0.173.0",++ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.173.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"++ }++ },++ "servers": [++ {++ "url": "https://openvibe.zone"++ }++ ],++ "paths": {++ "/api/v1/projects/{project_id}/zones": {++ "get": {++ "operationId": "get_api_v1_projects_project_id_zones",++ "summary": "List a project's object zones: GET /api/v1/projects/:project_id/zones, filtered by env and paged.",++ "description": "**zone.zone.list** (public): List a project's object zones: GET /api/v1/projects/:project_id/zones, filtered by env and paged. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",++ "tags": [++ "zone.zone"++ ],++ "x-openvibe-capabilities": [++ "zone.zone.list"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "zone-control",++ "security": [++ {++ "openvibeToken": [++ "zone.zone.list"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/zone.object-zone-list-query.v1"++ },++ "parameters": [++ {++ "name": "project_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "env",++ "in": "query",++ "required": false,++ "schema": {++ "enum": [++ "sandbox",++ "production"++ ]++ }++ },++ {++ "name": "include_deleted",++ "in": "query",++ "required": false,++ "schema": {++ "enum": [++ true,++ false,++ "true",++ "false"++ ],++ "description": "Also list zones in state deleted. Default false."++ },++ "description": "Also list zones in state deleted. Default false."++ },++ {++ "name": "limit",++ "in": "query",++ "required": false,++ "schema": {++ "oneOf": [++ {++ "type": "integer",++ "minimum": 1,++ "maximum": 200++ },++ {++ "type": "string",++ "pattern": "^(?:[1-9][0-9]?|1[0-9]{2}|200)$"++ }++ ],++ "description": "Items per page, 1 to 200. A query string carries it as a string, which must name the same range."++ },++ "description": "Items per page, 1 to 200. A query string carries it as a string, which must name the same range."++ },++ {++ "name": "cursor",++ "in": "query",++ "required": false,++ "schema": {++ "type": "string",++ "maxLength": 512,++ "description": "next_cursor of the previous page."++ },++ "description": "next_cursor of the previous page."++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/zone.object-zone-list.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ },++ "post": {++ "operationId": "post_api_v1_projects_project_id_zones",++ "summary": "Create a named object zone in one environment of a project.",++ "description": "**zone.zone.create** (public): Create a named object zone in one environment of a project. POST /api/v1/projects/:project_id/zones with an Idempotency-Key header answers 201 with the zone. A name held by a provisioning or active zone of that environment answers 409 zone.name_taken, a name held by a deleting or deleted one answers 409 zone.name_retired, and going over the project's zones quota answers 403 zone.quota_exceeded. Zone provisions the zone in Media before it becomes active. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",++ "tags": [++ "zone.zone"++ ],++ "x-openvibe-capabilities": [++ "zone.zone.create"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "zone-control",++ "security": [++ {++ "openvibeToken": [++ "zone.zone.create"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/zone.object-zone-create-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "project_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/zone.object-zone.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",++ "summary": "Read OpenVibe.Zone's resource index (ADR-048): GET /api/v1/resources lists the zones Zone owns as a page of common.resource-summary@1 (common.resource-list-result@1, kind zone.object-zone, id zon_<ULI",++ "description": "**zone.resource.read** (first-party): Read OpenVibe.Zone's resource index (ADR-048): GET /api/v1/resources lists the zones Zone owns as a page of common.resource-summary@1 (common.resource-list-result@1, kind zone.object-zone, id zon_<ULID>, with the project_id of the Network project that owns it), and GET /api/v1/resources/:ovrn reads one. Every authority serves these for OpenVibe.Services to fan out and merge.",++ "tags": [++ "zone.resource"++ ],++ "x-openvibe-capabilities": [++ "zone.resource.read"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "zone.resource.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/common.resource-list-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/resources/{ovrn}": {++ "get": {++ "operationId": "get_api_v1_resources_ovrn",++ "summary": "Read OpenVibe.Zone's resource index (ADR-048): GET /api/v1/resources lists the zones Zone owns as a page of common.resource-summary@1 (common.resource-list-result@1, kind zone.object-zone, id zon_<ULI",++ "description": "**zone.resource.read** (first-party): Read OpenVibe.Zone's resource index (ADR-048): GET /api/v1/resources lists the zones Zone owns as a page of common.resource-summary@1 (common.resource-list-result@1, kind zone.object-zone, id zon_<ULID>, with the project_id of the Network project that owns it), and GET /api/v1/resources/:ovrn reads one. Every authority serves these for OpenVibe.Services to fan out and merge.",++ "tags": [++ "zone.resource"++ ],++ "x-openvibe-capabilities": [++ "zone.resource.read"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "zone.resource.read"++ ]++ }++ ],++ "parameters": [++ {++ "name": "ovrn",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/common.resource-list-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/resources/control": {++ "post": {++ "operationId": "post_api_v1_resources_control",++ "summary": "Control a zone through OpenVibe.Zone's control API (ADR-048, plan T13 step 10): POST /api/v1/resources/control takes one common.resource-control-request@1 naming a zone.object-zone by its OVRN, from a",++ "description": "**zone.resource.control** (first-party): Control a zone through OpenVibe.Zone's control API (ADR-048, plan T13 step 10): POST /api/v1/resources/control takes one common.resource-control-request@1 naming a zone.object-zone by its OVRN, from a first-party service (OpenVibe.Services) acting for the person in on_behalf_of, who must be an admin or owner of the project. Action: delete (recursive: the zone answers NoSuchBucket at once, Media tombstones and purges every object with no restore window, and the name is retired). The default zone, and a zone holding an object under a retention hold, are refused. Any other action is refused unsupported. dry_run decides and explains without applying. The first answer per (caller, project, idempotency_key) is kept and returned for a repeat; the same key with another request is refused. Answers common.resource-control-result@1.",++ "tags": [++ "zone.resource"++ ],++ "x-openvibe-capabilities": [++ "zone.resource.control"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "standard",++ "security": [++ {++ "openvibeToken": [++ "zone.resource.control"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/common.resource-control-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/common.resource-control-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/zones/{zone_id}": {++ "delete": {++ "operationId": "delete_api_v1_zones_zone_id",++ "summary": "Delete an object zone: DELETE /api/v1/zones/:zone_id answers 202 with the zone in state deleting.",++ "description": "**zone.zone.delete** (public): Delete an object zone: DELETE /api/v1/zones/:zone_id answers 202 with the zone in state deleting. A zone with objects needs recursive=true (else 409 zone.not_empty), a zone with a held object answers 409 zone.held, and the default zone answers 409 zone.default. Media tombstones every object at once, with no restore window, and erases every copy within 24 hours. A hold placed after the request delays only that object's purge. The name is retired, never reused. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",++ "tags": [++ "zone.zone"++ ],++ "x-openvibe-capabilities": [++ "zone.zone.delete"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "zone-control",++ "security": [++ {++ "openvibeToken": [++ "zone.zone.delete"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/zone.object-zone-delete-query.v1"++ },++ "parameters": [++ {++ "name": "zone_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "recursive",++ "in": "query",++ "required": false,++ "schema": {++ "enum": [++ true,++ false,++ "true",++ "false"++ ],++ "description": "Delete the zone's objects too. Default false."++ },++ "description": "Delete the zone's objects too. Default false."++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/zone.object-zone.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ },++ "get": {++ "operationId": "get_api_v1_zones_zone_id",++ "summary": "Read one object zone: GET /api/v1/zones/:zone_id.",++ "description": "**zone.zone.read** (public): Read one object zone: GET /api/v1/zones/:zone_id. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",++ "tags": [++ "zone.zone"++ ],++ "x-openvibe-capabilities": [++ "zone.zone.read"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "zone-control",++ "security": [++ {++ "openvibeToken": [++ "zone.zone.read"++ ]++ }++ ],++ "parameters": [++ {++ "name": "zone_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/zone.object-zone.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_zones_zone_id",++ "summary": "Change an object zone's quota_bytes or description: PATCH /api/v1/zones/:zone_id.",++ "description": "**zone.zone.manage** (public): Change an object zone's quota_bytes or description: PATCH /api/v1/zones/:zone_id. Name, environment and access are immutable. A quota change takes effect once Media acknowledges it. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",++ "tags": [++ "zone.zone"++ ],++ "x-openvibe-capabilities": [++ "zone.zone.manage"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "zone-control",++ "security": [++ {++ "openvibeToken": [++ "zone.zone.manage"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/zone.object-zone-update-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "zone_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/zone.object-zone.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/zones/{zone_id}/usage": {++ "get": {++ "operationId": "get_api_v1_zones_zone_id_usage",++ "summary": "Read an object zone's usage: GET /api/v1/zones/:zone_id/usage.",++ "description": "**zone.usage.read** (public): Read an object zone's usage: GET /api/v1/zones/:zone_id/usage. It answers Media's live-object snapshot and the month's hourly rollups. Zone never counts. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",++ "tags": [++ "zone.usage"++ ],++ "x-openvibe-capabilities": [++ "zone.usage.read"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "zone-control",++ "security": [++ {++ "openvibeToken": [++ "zone.usage.read"++ ]++ }++ ],++ "parameters": [++ {++ "name": "zone_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/zone.object-zone-usage.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ }++ },++ "components": {++ "schemas": {++ "common.resource-control-request.v1": {++ "title": "ResourceControlRequest",++ "description": "common.resource-control-request@1: one control operation OpenVibe.Services (or Actor's Console) sends to the authority that owns a resource (ADR-048, plan T13). Services never mutates another service's database; every create, update, start, stop, delete, resize, rotate, pair, grant or revoke is this request to the owning authority's control API, which decides and answers with a common.resource-control-result@1. A request names exactly one of resource (every action but create) or resource_kind (create, before the name exists). project_id is the tenancy boundary and equals the project segment of resource. idempotency_key makes a retry safe: the authority answers a repeated key with the first result. A sensitive action (money, publishing, deleting, physical control) is refused with confirmation_required until its owner approves; the retry carries that confirmation_id (roadmap WS-Z2). The cross-field rules are checked by contracts.resources.checkControlRequest.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "action",++ "project_id",++ "idempotency_key"++ ],++ "oneOf": [++ {++ "required": [++ "resource"++ ]++ },++ {++ "required": [++ "resource_kind"++ ]++ }++ ],++ "properties": {++ "action": {++ "enum": [++ "create",++ "update",++ "delete",++ "start",++ "stop",++ "suspend",++ "resume",++ "resize",++ "rotate",++ "pair",++ "grant",++ "revoke",++ "archive"++ ]++ },++ "resource": {++ "$ref": "#/components/schemas/common.resource-name.v1",++ "description": "The resource acted on; required for every action but create."++ },++ "resource_kind": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{0,31}\\.[a-z][a-z0-9-]{0,39}$",++ "description": "service.type (a common.resource-summary@1 kind), for create, before the resource name exists."++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "params": {++ "type": "object",++ "description": "Action-specific input, shaped by the authority's own contract (never redefined here)."++ },++ "idempotency_key": {++ "type": "string",++ "minLength": 8,++ "maxLength": 200++ },++ "on_behalf_of": {++ "$ref": "#/components/schemas/identity.subject-ref.v1",++ "description": "The subject whose grant the authority checks; the caller's own token says who sent it."++ },++ "confirmation_id": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 80,++ "description": "The approved confirmation from an earlier confirmation_required answer."++ },++ "dry_run": {++ "type": "boolean",++ "default": false,++ "description": "Decide and explain without applying."++ },++ "trace_id": {++ "type": "string",++ "pattern": "^[0-9a-f]{32}$"++ }++ }++ },++ "common.resource-control-result.v1": {++ "title": "ResourceControlResult",++ "description": "common.resource-control-result@1: the authority's answer to a common.resource-control-request@1 (ADR-048, plan T13). state is the outcome, not the resource's own state: done (applied), pending (accepted and asynchronous; the authority reports completion later on its events), refused (a policy or confirmation gate said no) or failed (an error). State rules: refused carries problem or confirmation_required; failed carries problem; done and pending carry neither; confirmation_required appears only on refused. result carries the authority's own response document and never another service's rows. The state rules are checked by contracts.resources.checkControlResult.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "action",++ "state",++ "at"++ ],++ "properties": {++ "action": {++ "type": "string"++ },++ "resource": {++ "$ref": "#/components/schemas/common.resource-name.v1"++ },++ "state": {++ "enum": [++ "done",++ "pending",++ "refused",++ "failed"++ ]++ },++ "result": {++ "type": "object"++ },++ "confirmation_required": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "confirmation_id",++ "reason"++ ],++ "properties": {++ "confirmation_id": {++ "type": "string",++ "maxLength": 80++ },++ "reason": {++ "type": "string"++ }++ },++ "description": "The action needs its owner's approval; retry the same request with this confirmation_id once approved."++ },++ "problem": {++ "$ref": "#/components/schemas/errors.problem.v1",++ "description": "RFC 9457 body when refused or failed."++ },++ "at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "common.resource-list-result.v1": {++ "title": "ResourceListResult",++ "description": "A page of the resource index (roadmap WS-Z7).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "resources",++ "next_cursor"++ ],++ "properties": {++ "resources": {++ "type": "array",++ "items": {++ "$ref": "#/components/schemas/common.resource-summary.v1"++ }++ },++ "next_cursor": {++ "type": [++ "string",++ "null"++ ]++ },++ "partial": {++ "type": "array",++ "description": "Authorities a merged index (OpenVibe.Services, ADR-048) could not read for this page: a slow or failing authority is omitted, never fails the page. Each entry names the authority and the problem code it answered. Added in 1.1.0; a single authority's own index leaves it out.",++ "items": {++ "type": "object",++ "required": [++ "service",++ "code"++ ],++ "additionalProperties": false,++ "properties": {++ "service": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,39}$"++ },++ "code": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"++ }++ }++ }++ }++ }++ },++ "common.resource-name.v1": {++ "title": "ResourceName",++ "description": "common.resource-name@1: the one stable, cross-service name of an OpenVibe resource (OVRN; ADR-034 section 2, ADR-048, plan T13): ovrn:<service>:<project_id>:<type>/<id>, for example ovrn:media:prj_01J0000000000000000000000A:object/med_01K0000000000000000000000B. The service segment is the authority that owns the resource; the project segment is its tenancy boundary; type is the second half of a common.resource-summary@1 kind and id is that summary's id, so the name composes from any resource summary. It is used in grant scopes, audit rows, event subjects, usage records, bill lines and console URLs. It never names a person, an app, a request, a session or an event. The pattern is the one common.usage-recorded@1 resource and common.resource-summary@1 ovrn already use.",++ "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}$",++ "not": {++ "pattern": "/(usr|gst|app|mod|svc|job|evt|req)_"++ }++ },++ "common.resource-summary.v1": {++ "title": "ResourceSummary",++ "description": "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.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "kind",++ "service",++ "state",++ "created_at"++ ],++ "properties": {++ "id": {++ "type": "string"++ },++ "kind": {++ "type": "string",++ "description": "service.kind, e.g. watch.watch, actor.actor, media.object, events.subscription"++ },++ "service": {++ "type": "string"++ },++ "project_id": {++ "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": "#/components/schemas/identity.subject-ref.v1"++ },++ "name": {++ "type": "string"++ },++ "state": {++ "type": "string"++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "updated_at": {++ "type": "string",++ "format": "date-time"++ },++ "usage": {++ "type": "object",++ "additionalProperties": {++ "type": "number",++ "minimum": 0++ },++ "properties": {},++ "description": "cost units by name (WS-Z1)"++ },++ "links": {++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "self": {++ "type": "string",++ "format": "uri"++ },++ "console": {++ "type": "string",++ "format": "uri"++ },++ "docs": {++ "type": "string",++ "format": "uri"++ }++ }++ }++ }++ },++ "errors.problem.v1": {++ "title": "Problem",++ "description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",++ "type": "object",++ "required": [++ "type",++ "title",++ "status",++ "code"++ ],++ "additionalProperties": true,++ "properties": {++ "type": {++ "type": "string",++ "format": "uri-reference"++ },++ "title": {++ "type": "string",++ "minLength": 1++ },++ "status": {++ "type": "integer",++ "minimum": 400,++ "maximum": 599++ },++ "detail": {++ "type": "string"++ },++ "instance": {++ "type": "string",++ "format": "uri-reference"++ },++ "code": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"++ },++ "request_id": {++ "type": "string"++ },++ "trace_id": {++ "type": "string",++ "pattern": "^[0-9a-f]{32}$"++ },++ "errors": {++ "type": "array",++ "items": {++ "type": "object",++ "required": [++ "message"++ ],++ "properties": {++ "path": {++ "type": "string"++ },++ "message": {++ "type": "string"++ }++ }++ }++ },++ "error": {++ "type": "string",++ "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."++ }++ }++ },++ "identity.subject-ref.v1": {++ "title": "SubjectRef",++ "description": "The one cross-platform reference to an actor. Issued by OpenVibe.Network. Never a service-local integer id.",++ "oneOf": [++ {++ "type": "object",++ "required": [++ "type",++ "id"++ ],++ "additionalProperties": false,++ "properties": {++ "type": {++ "const": "user"++ },++ "id": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ },++ "description": "UserSubject"++ },++ {++ "type": "object",++ "required": [++ "type",++ "id"++ ],++ "additionalProperties": false,++ "properties": {++ "type": {++ "const": "guest"++ },++ "id": {++ "type": "string",++ "pattern": "^gst_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ },++ "description": "GuestSubject"++ },++ {++ "type": "object",++ "required": [++ "type",++ "id"++ ],++ "additionalProperties": false,++ "properties": {++ "type": {++ "const": "app"++ },++ "id": {++ "type": "string",++ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ },++ "description": "AppSubject"++ },++ {++ "type": "object",++ "required": [++ "type",++ "id"++ ],++ "additionalProperties": false,++ "properties": {++ "type": {++ "const": "mod"++ },++ "id": {++ "type": "string",++ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ },++ "description": "ModSubject"++ },++ {++ "type": "object",++ "required": [++ "type",++ "id"++ ],++ "additionalProperties": false,++ "properties": {++ "type": {++ "enum": [++ "service",++ "system"++ ]++ },++ "id": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,39}$"++ }++ },++ "description": "PrincipalSubject"++ },++ {++ "type": "object",++ "required": [++ "type",++ "id"++ ],++ "additionalProperties": false,++ "properties": {++ "type": {++ "const": "agent"++ },++ "id": {++ "type": "string",++ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ },++ "description": "AgentSubject: an agent (OpenVibe.Actor, or a developer app's agent) acting under grants its owner delegated (roadmap WS-Z2). In an event envelope, on_behalf_of names the owner."++ }++ ]++ },++ "zone.object-zone-create-request.v1": {++ "title": "ObjectZoneCreateRequest",++ "description": "zone.object-zone-create-request@1 (ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "env",++ "name"++ ],++ "properties": {++ "env": {++ "enum": [++ "sandbox",++ "production"++ ]++ },++ "name": {++ "type": "string",++ "minLength": 3,++ "maxLength": 63,++ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",++ "not": {++ "const": "default"++ }++ },++ "description": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 200++ },++ "quota_bytes": {++ "type": [++ "integer",++ "null"++ ],++ "minimum": 1++ }++ }++ },++ "zone.object-zone-delete-query.v1": {++ "title": "ObjectZoneDeleteQuery",++ "description": "zone.object-zone-delete-query@1 (ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "recursive": {++ "enum": [++ true,++ false,++ "true",++ "false"++ ],++ "description": "Delete the zone's objects too. Default false."++ }++ }++ },++ "zone.object-zone-list-query.v1": {++ "title": "ObjectZoneListQuery",++ "description": "zone.object-zone-list-query@1 (ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "env": {++ "enum": [++ "sandbox",++ "production"++ ]++ },++ "include_deleted": {++ "enum": [++ true,++ false,++ "true",++ "false"++ ],++ "description": "Also list zones in state deleted. Default false."++ },++ "limit": {++ "oneOf": [++ {++ "type": "integer",++ "minimum": 1,++ "maximum": 200++ },++ {++ "type": "string",++ "pattern": "^(?:[1-9][0-9]?|1[0-9]{2}|200)$"++ }++ ],++ "description": "Items per page, 1 to 200. A query string carries it as a string, which must name the same range."++ },++ "cursor": {++ "type": "string",++ "maxLength": 512,++ "description": "next_cursor of the previous page."++ }++ }++ },++ "zone.object-zone-list.v1": {++ "title": "ObjectZoneList",++ "description": "zone.object-zone-list@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "zones",++ "next_cursor"++ ],++ "properties": {++ "zones": {++ "type": "array",++ "maxItems": 200,++ "items": {++ "$ref": "#/components/schemas/zone.object-zone.v1"++ }++ },++ "next_cursor": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 512,++ "description": "Pass as cursor for the next page; null on the last page."++ }++ }++ },++ "zone.object-zone-update-request.v1": {++ "title": "ObjectZoneUpdateRequest",++ "description": "zone.object-zone-update-request@1 (ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.",++ "type": "object",++ "additionalProperties": false,++ "minProperties": 1,++ "properties": {++ "description": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 200++ },++ "quota_bytes": {++ "type": [++ "integer",++ "null"++ ],++ "minimum": 1,++ "description": "null removes the zone's own ceiling, so only the environment quota applies."++ }++ }++ },++ "zone.object-zone-usage.v1": {++ "title": "ObjectZoneUsage",++ "description": "zone.object-zone-usage@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "zone_id",++ "ovrn",++ "current",++ "window_start",++ "window_end",++ "series"++ ],++ "properties": {++ "zone_id": {++ "type": "string",++ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "ovrn": {++ "type": "string",++ "pattern": "^ovrn:zone:prj_[0-9A-HJKMNP-TV-Z]{26}:object-zone/zon_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "current": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "stored_bytes",++ "objects",++ "as_of"++ ],++ "properties": {++ "stored_bytes": {++ "type": "integer",++ "minimum": 0++ },++ "objects": {++ "type": "integer",++ "minimum": 0++ },++ "as_of": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "window_start": {++ "type": "string",++ "format": "date-time",++ "description": "The start of the current UTC month."++ },++ "window_end": {++ "type": "string",++ "format": "date-time",++ "description": "The end of the last closed hour that Media has reported."++ },++ "series": {++ "type": "array",++ "maxItems": 32,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "capability",++ "unit",++ "quantity"++ ],++ "properties": {++ "capability": {++ "type": "string",++ "pattern": "^media\\.object\\.[a-z0-9_]+$"++ },++ "unit": {++ "enum": [++ "byte_hours",++ "requests",++ "egress_bytes"++ ]++ },++ "quantity": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ }++ }++ },++ "zone.object-zone.v1": {++ "title": "ObjectZone",++ "description": "zone.object-zone@1 (ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "ovrn",++ "project_id",++ "env",++ "name",++ "default",++ "access",++ "state",++ "media_namespace",++ "quota_bytes",++ "revision",++ "created_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "ovrn": {++ "type": "string",++ "pattern": "^ovrn:zone:prj_[0-9A-HJKMNP-TV-Z]{26}:object-zone/zon_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The resource name used in grants, audit, events, usage records and bills (ADR-034 section 2)."++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "env": {++ "enum": [++ "sandbox",++ "production"++ ],++ "description": "Fixed at creation. A sandbox key never resolves a production zone."++ },++ "name": {++ "type": "string",++ "minLength": 3,++ "maxLength": 63,++ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",++ "description": "The S3 bucket name. It is unique among all the zones of one project environment, deleted ones included: a deleted zone's name is retired, never given to another zone. It is valid as an S3 bucket name."++ },++ "default": {++ "type": "boolean",++ "description": "The environment's default zone, named `default`. Its media_namespace is the environment root."++ },++ "description": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 200++ },++ "access": {++ "enum": [++ "private",++ "per-object"++ ],++ "description": "private: every named zone in v1. Its objects never have a public_url and never enter a public cache. per-object: only the default zone, whose existing objects keep their own visibility (public, unlisted or private), public URLs and cached copies, as media.object@1 allows. The S3 surface never serves an object without a signature, in either kind of zone. Publication of a named zone would add a value in a later minor version."++ },++ "state": {++ "enum": [++ "provisioning",++ "active",++ "deleting",++ "deleted"++ ],++ "description": "provisioning: Media has not yet acknowledged the zone. active: it serves requests. deleting: requests answer NoSuchBucket while Media tombstones the objects and erases every copy. deleted: no object or copy remains. The record is kept, and its name stays retired."++ },++ "media_namespace": {++ "type": "string",++ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}(\\.sandbox)?(\\.zon_[0-9A-HJKMNP-TV-Z]{26})?$",++ "description": "Where Media keeps the zone's objects. It is the environment root for the default zone, and <root>.<id> for every other zone."++ },++ "quota_bytes": {++ "type": [++ "integer",++ "null"++ ],++ "minimum": 1,++ "description": "An optional byte ceiling for this zone. Media enforces it together with the project environment's byte quota, and a write over either is refused with QuotaExceeded. null means only the environment quota applies."++ },++ "usage": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "stored_bytes",++ "objects",++ "as_of"++ ],++ "description": "A snapshot of the live objects, as Media reports it. Zone never counts. Billed usage comes from Media's hourly media.usage.recorded rollups, whose dimension is this id in lowercase.",++ "properties": {++ "stored_bytes": {++ "type": "integer",++ "minimum": 0++ },++ "objects": {++ "type": "integer",++ "minimum": 0++ },++ "as_of": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "deletion": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "requested_at",++ "objects_remaining",++ "held_objects",++ "completed_at"++ ],++ "description": "Progress of a zone deletion, as Media reports it.",++ "properties": {++ "requested_at": {++ "type": "string",++ "format": "date-time"++ },++ "objects_remaining": {++ "type": "integer",++ "minimum": 0,++ "description": "Objects not yet tombstoned, plus tombstoned objects with a copy not yet confirmed erased."++ },++ "held_objects": {++ "type": "integer",++ "minimum": 0,++ "description": "Objects whose erasure waits for a retention hold (ADR-006). They are already unreadable."++ },++ "completed_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ }++ }++ },++ "revision": {++ "type": "integer",++ "minimum": 1,++ "description": "Raised by every change. Zone sends it when it provisions the change in Media, and Media applies only a higher revision."++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "updated_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ }++ },++ "allOf": [++ {++ "if": {++ "properties": {++ "default": {++ "const": true++ }++ },++ "required": [++ "default"++ ]++ },++ "then": {++ "properties": {++ "name": {++ "const": "default"++ },++ "access": {++ "const": "per-object"++ },++ "media_namespace": {++ "type": "string",++ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}(\\.sandbox)?$"++ }++ }++ },++ "else": {++ "properties": {++ "name": {++ "not": {++ "const": "default"++ }++ },++ "access": {++ "const": "private"++ },++ "media_namespace": {++ "type": "string",++ "pattern": "\\.zon_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++ }++ },++ {++ "if": {++ "properties": {++ "env": {++ "const": "sandbox"++ }++ },++ "required": [++ "env"++ ]++ },++ "then": {++ "properties": {++ "media_namespace": {++ "type": "string",++ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}\\.sandbox(\\.|$)"++ }++ }++ },++ "else": {++ "properties": {++ "media_namespace": {++ "type": "string",++ "not": {++ "pattern": "\\.sandbox(\\.|$)"++ }++ }++ }++ }++ },++ {++ "if": {++ "properties": {++ "state": {++ "enum": [++ "deleting",++ "deleted"++ ]++ }++ },++ "required": [++ "state"++ ]++ },++ "then": {++ "required": [++ "deletion"++ ]++ }++ },++ {++ "if": {++ "properties": {++ "state": {++ "const": "deleted"++ }++ },++ "required": [++ "state"++ ]++ },++ "then": {++ "properties": {++ "deletion": {++ "type": "object",++ "properties": {++ "objects_remaining": {++ "const": 0++ },++ "held_objects": {++ "const": 0++ },++ "completed_at": {++ "type": "string"++ }++ }++ }++ }++ }++ }++ ]++ }++ },++ "securitySchemes": {++ "openvibeToken": {++ "type": "http",++ "scheme": "bearer",++ "bearerFormat": "JWT",++ "description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."++ }++ }++ }++}@@@ -175561,7 +175561,7 @@ export type NetworkConfirmationChangedPayload = {/** [email protected] (owner: zone) *//**-- * zone.object-zone@1 (PLANNED; ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.++ * zone.object-zone@1 (ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.*/export type ObjectZone = {[k: string]: unknown | undefined;@@@ -175634,7 +175634,7 @@ export type ObjectZone = {/** [email protected] (owner: zone) *//**-- * zone.object-zone-create-request@1 (PLANNED; ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.++ * zone.object-zone-create-request@1 (ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.*/export interface ObjectZoneCreateRequest {env: "sandbox" | "production";@@@ -175645,7 +175645,7 @@ export interface ObjectZoneCreateRequest {/** [email protected] (owner: zone) *//**-- * zone.object-zone-update-request@1 (PLANNED; ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.++ * zone.object-zone-update-request@1 (ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.*/export interface ObjectZoneUpdateRequest {description?: string | null;@@@ -175657,7 +175657,7 @@ export interface ObjectZoneUpdateRequest {/** [email protected] (owner: zone) *//**-- * zone.object-zone-list-query@1 (PLANNED; ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.++ * zone.object-zone-list-query@1 (ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.*/export interface ObjectZoneListQuery {env?: "sandbox" | "production";@@@ -175677,7 +175677,7 @@ export interface ObjectZoneListQuery {/** [email protected] (owner: zone) *//**-- * zone.object-zone-list@1 (PLANNED; ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.++ * zone.object-zone-list@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.*/export interface ObjectZoneList {/**@@@ -175692,7 +175692,7 @@ export interface ObjectZoneList {/** [email protected] (owner: zone) *//**-- * zone.object-zone-delete-query@1 (PLANNED; ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.++ * zone.object-zone-delete-query@1 (ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.*/export interface ObjectZoneDeleteQuery {/**@@@ -175703,7 +175703,7 @@ export interface ObjectZoneDeleteQuery {/** [email protected] (owner: zone) *//**-- * zone.object-zone-usage@1 (PLANNED; ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.++ * zone.object-zone-usage@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.*/export interface ObjectZoneUsage {zone_id: string;@@@ -184072,3 +184072,333 @@ export interface PlayPlayerAchievements {unlocked_at: string;}[];}++++/** [email protected] (owner: media) */++/**++ * media.access-key@1 (ADR-031, amended 2026-10-14): one S3 access key of OpenVibe.Media's S3 surface (https://s3.openvibe.media). It is bound to one project environment, reads and/or writes, and may be scoped to some zones of that environment (zone.object-zone@1 ids; empty means every zone). Media issues and verifies it: SigV4 needs the secret, so the party that checks signatures keeps it, encrypted, and shows it once (media.access-key-issued@1). The secret is never part of this record. A revoked key is refused at once; a key scoped only to a zone that is deleted is revoked with it. More fields may be added.++ */++export interface AccessKey {++ /**++ * The S3 access key id.++ */++ id: string;++ project_id: string;++ env: "sandbox" | "production";++ /**++ * What uses the key, as the person who issued it named it.++ */++ name: string;++ permissions: "read" | "write" | "read-write";++ /**++ * The zones it may reach; empty = every zone of the environment.++ *++ * @maxItems 20++ */++ zones:++ | []++ | [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]++ | [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, 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,++ 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,++ 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,++ 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++ ];++ created_by: string;++ created_at: string;++ /**++ * The last signed request, to the minute.++ */++ last_used_at: string | null;++ revoked_at: string | null;++}++++/** [email protected] (owner: media) */++/**++ * media.access-key-issue-request@1 (ADR-031): the body of POST /api/v1/projects/:project_id/access-keys on OpenVibe.Media (capability media.access_key.manage), sent by a first-party service (OpenVibe.Zone, OpenVibe.Services) for the person in on_behalf_of, who must be an admin or owner of the project (Network decides). The answer is 201 media.access-key-issued@1. A zone that is not an active zone of this project environment answers 404 media.access_key.unknown_zone; more than 50 live keys in one environment answer 409 media.access_key.limit.++ */++export interface AccessKeyIssueRequest {++ env: "sandbox" | "production";++ name: string;++ permissions?: "read" | "write" | "read-write";++ /**++ * @maxItems 20++ */++ zones?:++ | []++ | [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]++ | [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, 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,++ 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,++ 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,++ 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++ ];++ on_behalf_of: string;++}++++/** [email protected] (owner: media) */++/**++ * media.access-key-issued@1 (ADR-031): the 201 of POST /api/v1/projects/:project_id/access-keys. `secret` is the S3 secret access key; it is in this answer only, and Media never shows it again. A caller relays it to the person once and keeps no copy.++ */++export interface AccessKeyIssued {++ key: AccessKey;++ secret: string;++}++++/** [email protected] (owner: media) */++/**++ * media.access-key-list@1 (ADR-031): GET /api/v1/projects/:project_id/access-keys (capability media.access_key.manage, for a project admin in on_behalf_of): every key of the project, newest first, revoked ones included. No secret is ever listed.++ */++export interface AccessKeyList {++ /**++ * @maxItems 500++ */++ keys: AccessKey[];++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "id": "media.access_key.manage",++ "version": "1.0",++ "owner": "media",++ "status": "active",++ "visibility": "first-party",++ "sensitive": true,++ "description": "Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies). A first-party service (OpenVibe.Zone, OpenVibe.Services) calls for the person in on_behalf_of, who must be an admin or owner of the project (Media asks Network, network.project.member.read). POST /api/v1/projects/:project_id/access-keys takes media.access-key-issue-request@1 and answers 201 media.access-key-issued@1, the only answer that carries the secret; GET lists media.access-key-list@1; DELETE /:key_id revokes at once. Loopback only.",++ "permissions": [++ "keys:manage"++ ],++ "resourceConstraints": [++ "project"++ ],++ "quotaClass": "internal-write",++ "events": [],++ "implementedBy": [++ "POST /api/v1/projects/:project_id/access-keys",++ "GET /api/v1/projects/:project_id/access-keys",++ "DELETE /api/v1/projects/:project_id/access-keys/:key_id"++ ],++ "inputSchema": "media.access-key-issue-request@1",++ "outputSchema": "media.access-key-issued@1"++}nnew file mode 100644@@@ -0,0 +1,32 @@++{++ "id": "media.object.s3",++ "version": "1.0",++ "owner": "media",++ "status": "active",++ "visibility": "public",++ "description": "OpenVibe.Media's S3 surface (ADR-031), path style at https://s3.openvibe.media/<zone name>/<key>: PutObject (one request, aws-chunked bodies included, Content-MD5 and x-amz-checksum-* checked), GetObject (ranges), HeadObject, DeleteObject, DeleteObjects, ListObjectsV2 and ListObjects (prefix, delimiter, pages), ListBuckets, HeadBucket, GetBucketLocation, and presigned GET and PUT (SigV4 query, 7 days at most). Everything else answers NotImplemented (multipart, copy, ACLs, policies, versioning; buckets are made and deleted only through OpenVibe.Zone). It is reached with a media.access-key@1 rather than a Network token: the key fixes the project and environment, so a bucket name names one zone of that environment, and a key scoped to zones reaches only those. A request is authorised as the project's app would be: the same quotas (the zone's and the environment's), the same objects as the native API, and Media's one deletion path (a held object is neither deleted nor overwritten). Responses carry a sandbox Content-Security-Policy.",++ "permissions": [++ "objects:read",++ "objects:write"++ ],++ "resourceConstraints": [++ "project"++ ],++ "quotaClass": "standard",++ "events": [++ "media.object.uploaded",++ "media.object.deleted"++ ],++ "implementedBy": [++ "GET s3:/",++ "HEAD s3:/:zone",++ "GET s3:/:zone",++ "POST s3:/:zone?delete",++ "PUT s3:/:zone/:key",++ "GET s3:/:zone/:key",++ "HEAD s3:/:zone/:key",++ "DELETE s3:/:zone/:key"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "common.no-body@1"++}nnew file mode 100644@@@ -0,0 +1,23 @@++{++ "id": "media.zone.provision",++ "version": "1.0",++ "owner": "media",++ "status": "active",++ "visibility": "first-party",++ "sensitive": true,++ "description": "Provision a zone in OpenVibe.Media (ADR-031 amendment 2026-10-02), granted only to OpenVibe.Zone: PUT /internal/zones/:zone_id with the zone.object-zone@1 record Zone owns. Media applies a record only when its revision is higher than the one it holds, checks it with contracts.zones.checkObjectZone, and refuses a name another zone of the environment holds (409 media.zone.name_retired) and any change of project, environment, name or namespace (409 media.zone.immutable). Media keeps a projection of each zone to resolve S3 bucket names, enforce the zone's quota_bytes beside the environment's, and answer NoSuchBucket for a zone that is deleting or deleted. GET /internal/zones/:zone_id reports the live objects and, while the zone is deleting, the objects remaining and held; Media tombstones and purges a deleting zone's objects (no restore window) and records when none remain. Loopback only.",++ "permissions": [++ "zones:provision"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "internal-write",++ "events": [],++ "implementedBy": [++ "PUT /internal/zones/:zone_id",++ "GET /internal/zones/:zone_id"++ ],++ "inputSchema": "zone.object-zone@1",++ "outputSchema": "common.no-body@1"++}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "id": "zone.resource.control",++ "version": "1.0",++ "owner": "zone",++ "status": "active",++ "visibility": "first-party",++ "sensitive": true,++ "description": "Control a zone through OpenVibe.Zone's control API (ADR-048, plan T13 step 10): POST /api/v1/resources/control takes one common.resource-control-request@1 naming a zone.object-zone by its OVRN, from a first-party service (OpenVibe.Services) acting for the person in on_behalf_of, who must be an admin or owner of the project. Action: delete (recursive: the zone answers NoSuchBucket at once, Media tombstones and purges every object with no restore window, and the name is retired). The default zone, and a zone holding an object under a retention hold, are refused. Any other action is refused unsupported. dry_run decides and explains without applying. The first answer per (caller, project, idempotency_key) is kept and returned for a repeat; the same key with another request is refused. Answers common.resource-control-result@1.",++ "permissions": [++ "resources:control"++ ],++ "resourceConstraints": [++ "project"++ ],++ "quotaClass": "standard",++ "events": [],++ "implementedBy": [++ "POST /api/v1/resources/control"++ ],++ "inputSchema": "common.resource-control-request@1",++ "outputSchema": "common.resource-control-result@1"++}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "id": "zone.resource.read",++ "version": "1.0",++ "owner": "zone",++ "status": "active",++ "visibility": "first-party",++ "description": "Read OpenVibe.Zone's resource index (ADR-048): GET /api/v1/resources lists the zones Zone owns as a page of common.resource-summary@1 (common.resource-list-result@1, kind zone.object-zone, id zon_<ULID>, with the project_id of the Network project that owns it), and GET /api/v1/resources/:ovrn reads one. Every authority serves these for OpenVibe.Services to fan out and merge.",++ "permissions": [++ "resources:read"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "standard",++ "events": [],++ "implementedBy": [++ "GET /api/v1/resources",++ "GET /api/v1/resources/:ovrn"++ ],++ "inputSchema": "common.no-body@1",++ "outputSchema": "common.resource-list-result@1"++}@@@ -2,9 +2,9 @@"id": "zone.usage.read","version": "1.0","owner": "zone",-- "status": "planned",++ "status": "active","visibility": "public",-- "description": "PLANNED (ADR-031 amendment 2026-10-02): Read an object zone's usage: GET /api/v1/zones/:zone_id/usage. It answers Media's live-object snapshot and the month's hourly rollups. Zone never counts. It becomes active, and enters generated/openapi/zone.json, when OpenVibe.Zone implements the route.",++ "description": "Read an object zone's usage: GET /api/v1/zones/:zone_id/usage. It answers Media's live-object snapshot and the month's hourly rollups. Zone never counts. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.","outputSchema": "zone.object-zone-usage@1","permissions": ["zone:read"@@@ -14,5 +14,8 @@],"quotaClass": "zone-control","events": [],-- "implementedBy": []++ "implementedBy": [++ "GET /api/v1/zones/:zone_id/usage"++ ],++ "inputSchema": "common.no-body@1"}@@@ -2,9 +2,9 @@"id": "zone.zone.create","version": "1.0","owner": "zone",-- "status": "planned",++ "status": "active","visibility": "public",-- "description": "PLANNED (ADR-031 amendment 2026-10-02): Create a named object zone in one environment of a project. POST /api/v1/projects/:project_id/zones with an Idempotency-Key header answers 201 with the zone. A name held by a provisioning or active zone of that environment answers 409 zone.name_taken, a name held by a deleting or deleted one answers 409 zone.name_retired, and going over the project's zones quota answers 403 zone.quota_exceeded. Zone provisions the zone in Media before it becomes active. It becomes active, and enters generated/openapi/zone.json, when OpenVibe.Zone implements the route.",++ "description": "Create a named object zone in one environment of a project. POST /api/v1/projects/:project_id/zones with an Idempotency-Key header answers 201 with the zone. A name held by a provisioning or active zone of that environment answers 409 zone.name_taken, a name held by a deleting or deleted one answers 409 zone.name_retired, and going over the project's zones quota answers 403 zone.quota_exceeded. Zone provisions the zone in Media before it becomes active. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.","inputSchema": "zone.object-zone-create-request@1","outputSchema": "zone.object-zone@1","permissions": [@@@ -15,5 +15,7 @@],"quotaClass": "zone-control","events": [],-- "implementedBy": []++ "implementedBy": [++ "POST /api/v1/projects/:project_id/zones"++ ]}@@@ -2,10 +2,10 @@"id": "zone.zone.delete","version": "1.0","owner": "zone",-- "status": "planned",++ "status": "active","visibility": "public","sensitive": true,-- "description": "PLANNED (ADR-031 amendment 2026-10-02): Delete an object zone: DELETE /api/v1/zones/:zone_id answers 202 with the zone in state deleting. A zone with objects needs recursive=true (else 409 zone.not_empty), a zone with a held object answers 409 zone.held, and the default zone answers 409 zone.default. Media tombstones every object at once, with no restore window, and erases every copy within 24 hours. A hold placed after the request delays only that object's purge. The name is retired, never reused. It becomes active, and enters generated/openapi/zone.json, when OpenVibe.Zone implements the route.",++ "description": "Delete an object zone: DELETE /api/v1/zones/:zone_id answers 202 with the zone in state deleting. A zone with objects needs recursive=true (else 409 zone.not_empty), a zone with a held object answers 409 zone.held, and the default zone answers 409 zone.default. Media tombstones every object at once, with no restore window, and erases every copy within 24 hours. A hold placed after the request delays only that object's purge. The name is retired, never reused. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.","inputSchema": "zone.object-zone-delete-query@1","outputSchema": "zone.object-zone@1","permissions": [@@@ -16,5 +16,7 @@],"quotaClass": "zone-control","events": [],-- "implementedBy": []++ "implementedBy": [++ "DELETE /api/v1/zones/:zone_id"++ ]}@@@ -2,9 +2,9 @@"id": "zone.zone.list","version": "1.0","owner": "zone",-- "status": "planned",++ "status": "active","visibility": "public",-- "description": "PLANNED (ADR-031 amendment 2026-10-02): List a project's object zones: GET /api/v1/projects/:project_id/zones, filtered by env and paged. It becomes active, and enters generated/openapi/zone.json, when OpenVibe.Zone implements the route.",++ "description": "List a project's object zones: GET /api/v1/projects/:project_id/zones, filtered by env and paged. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.","inputSchema": "zone.object-zone-list-query@1","outputSchema": "zone.object-zone-list@1","permissions": [@@@ -15,5 +15,7 @@],"quotaClass": "zone-control","events": [],-- "implementedBy": []++ "implementedBy": [++ "GET /api/v1/projects/:project_id/zones"++ ]}@@@ -2,9 +2,9 @@"id": "zone.zone.manage","version": "1.0","owner": "zone",-- "status": "planned",++ "status": "active","visibility": "public",-- "description": "PLANNED (ADR-031 amendment 2026-10-02): Change an object zone's quota_bytes or description: PATCH /api/v1/zones/:zone_id. Name, environment and access are immutable. A quota change takes effect once Media acknowledges it. It becomes active, and enters generated/openapi/zone.json, when OpenVibe.Zone implements the route.",++ "description": "Change an object zone's quota_bytes or description: PATCH /api/v1/zones/:zone_id. Name, environment and access are immutable. A quota change takes effect once Media acknowledges it. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.","inputSchema": "zone.object-zone-update-request@1","outputSchema": "zone.object-zone@1","permissions": [@@@ -15,5 +15,7 @@],"quotaClass": "zone-control","events": [],-- "implementedBy": []++ "implementedBy": [++ "PATCH /api/v1/zones/:zone_id"++ ]}@@@ -2,9 +2,9 @@"id": "zone.zone.read","version": "1.0","owner": "zone",-- "status": "planned",++ "status": "active","visibility": "public",-- "description": "PLANNED (ADR-031 amendment 2026-10-02): Read one object zone: GET /api/v1/zones/:zone_id. It becomes active, and enters generated/openapi/zone.json, when OpenVibe.Zone implements the route.",++ "description": "Read one object zone: GET /api/v1/zones/:zone_id. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.","outputSchema": "zone.object-zone@1","permissions": ["zone:read"@@@ -14,5 +14,8 @@],"quotaClass": "zone-control","events": [],-- "implementedBy": []++ "implementedBy": [++ "GET /api/v1/zones/:zone_id"++ ],++ "inputSchema": "common.no-body@1"}@@@ -8,7 +8,8 @@"status": "beta","repository": "OpenVibers/OpenVibe.Media","domains": [-- "openvibe.media"++ "openvibe.media",++ "s3.openvibe.media"],"publicOrigin": "https://openvibe.media","health": "/healthz",@@@ -27,6 +28,7 @@"position": 4},"capabilities": [++ "media.access_key.manage","media.avatar.ingest","media.derivative.create","media.derivative.read",@@@ -35,10 +37,12 @@"media.object.delete","media.object.list","media.object.read",++ "media.object.s3","media.object.upload",-- "media.resource.read","media.resource.control",-- "media.upload.create"++ "media.resource.read",++ "media.upload.create",++ "media.zone.provision"],"eventsProduced": ["media.clip.failed",@@@ -1,21 +1,20 @@{-- "version": "0.0.0",++ "version": "0.1.0","contractRanges": {-- "openvibe-contracts": ">=0.87.0 <1.0.0"++ "openvibe-contracts": ">=0.173.0 <1.0.0"},"id": "zone","name": "OpenVibe.Zone",-- "status": "placeholder",++ "status": "alpha","repository": "OpenVibers/OpenVibe.Zone","domains": ["openvibe.zone","api.openvibe.zone"],-- "publicOrigin": "https://api.openvibe.zone",++ "publicOrigin": "https://openvibe.zone","exposure": {-- "state": "placeholder",-- "publicSite": null,-- "note": "planned (ADR-031 amendment 2026-10-02); nothing is built or deployed, and no OpenVibe service serves openvibe.zone yet"++ "state": "live",++ "publicSite": "service"},"capabilities": ["zone.zone.create",@@@ -23,30 +22,73 @@"zone.zone.read","zone.zone.manage","zone.zone.delete",-- "zone.usage.read"++ "zone.usage.read",++ "zone.resource.read",++ "zone.resource.control"],"eventsProduced": [],"eventsConsumed": [],-- "namespacesOwned": [],++ "namespacesOwned": [++ "zone.*"++ ],"lifecycle": {"liveness": {-- "none": "not built yet; nothing listens on a loopback address"++ "endpoint": "/api/health",++ "means": "the process is up and answers HTTP; the database is what ready checks"},"shutdown": {-- "none": "not built yet; nothing runs or drains"++ "signal": "SIGTERM",++ "deadlineSeconds": 10,++ "drains": [++ "HTTP server closed",++ "deletion poller stopped",++ "Network key refresher stopped",++ "store closed"++ ]},"startupRecovery": {-- "none": "not built yet; nothing starts"++ "resumes": [++ {++ "kind": "sessions",++ "what": "sign-in sessions are cookies verified against Network's keys, fetched again at boot"++ },++ {++ "kind": "state",++ "what": "every zone is a committed row with a revision; a zone still provisioning, or deleting, is pushed to Media again (Media applies only a higher revision)"++ },++ {++ "kind": "jobs",++ "what": "the deletion poller resumes every zone in state deleting and marks it deleted once Media reports nothing left"++ }++ ]},"rollback": {-- "none": "not built yet; nothing is released or rolled back"++ "conditions": [++ "automatic: ovhost deploy puts the previous sha back and restarts again when the ready URL does not answer 2xx within ready.timeoutSeconds after the restart (exit 3)",++ "manual: ovhost rollback <service> --to <sha>"++ ],++ "window": "automatic only while ovhost waits for readiness after the restart; manual afterwards, as long as no blocker applies",++ "blockers": {++ "none": "migration 0001 only creates Zone's own tables; Media keeps serving every zone from its projection while Zone is down"++ }},"contracts": {-- "range": ">=0.87.0 <1.0.0"++ "range": ">=0.173.0 <1.0.0"},"leases": {-- "none": "not built yet; nothing runs"++ "none": "no claims: one process serves the site, the API and the deletion poller"}},-- "notes": "Named object zones per project environment (ADR-031 amendment 2026-10-02). Zone owns the zone record and its control API in its own PostgreSQL database (ADR-035). OpenVibe.Media owns the objects, bytes, replicas, S3 and native data APIs, quota enforcement, usage counting and the one deletion path. Planned: nothing is built."++ "notes": "Port 5050, unit openvibe-zone.service, openvibe.zone (its own certificate; api.openvibe.zone waits for its DNS record). Named object zones per project environment (ADR-031 amendment 2026-10-02): Zone owns the zone record, its name (never reused), state, quota setting and control API in its own PostgreSQL database (ov_zone, ADR-035), and provisions every change in OpenVibe.Media, which owns the objects, bytes, replicas, the S3 and native data APIs, quota enforcement, usage counting, the access keys and the one deletion path. Zone never lists keys or holds bytes. Its site is the storage console: a project's zones, their usage, S3 access keys (issued by Media), and how to use them.",++ "health": "/api/health",++ "ready": "/api/ready",++ "internalOrigin": "http://127.0.0.1:5050",++ "site": {++ "name": "Zone",++ "icon": "zone",++ "tagline": "Private S3-compatible storage for your projects.",++ "what": "Named storage zones for each project and environment: buckets that the AWS SDKs, rclone and backup tools reach over S3, with access keys you scope to a zone and quotas you set.",++ "legalProfile": "ugc",++ "position": 36++ }}@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.172.2",++ "version": "0.173.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,24 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -4,6 +4,24 @@ 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.173.0 — 2026-10-14
+
+- **OpenVibe.Zone is live, and so is Media's S3 surface** (ADR-031 and its zone amendment). Zone owns named object
+ zones per project environment at https://openvibe.zone (port 5050), provisioning each change in Media by revision.
+ Media serves the zones as S3 buckets and keeps the objects.
+- Zone's `zone.*` contracts and capabilities are now active, and two more are added: `zone.resource.read` and
+ `zone.resource.control` (delete, for Services).
+- **Media has three new capabilities:**
+ - `media.object.s3`: the S3 door, reached with an access key;
+ - `media.access_key.manage`: issue, list and revoke keys, for a project admin;
+ - `media.zone.provision`: Zone only.
+- **Access-key schemas:** `media.access-key@1`, `media.access-key-issue-request@1`, `media.access-key-issued@1` (the
+ only answer with the secret) and `media.access-key-list@1`.
+- **ADR-031 amendment 2026-10-14:**
+ - Media issues the keys, not Codes: the verifier must hold the secret, and Codes became the coding harness.
+ - The S3 surface is served by Host.
+ - Zone serves on its apex until api.openvibe.zone has DNS.
+
## 0.172.2 — 2026-10-14
## 0.172.2 — 2026-10-14
- **Who consumes `network.subject.merged`, written down.** Live, Chat, Community, Media and Games have folded merged
- **Who consumes `network.subject.merged`, written down.** Live, Chat, Community, Media and Games have folded merged
@@ -5969,7 +5969,7 @@
@@ -5969,7 +5969,7 @@
"schema": "zone/object-zone.v1.json",
"schema": "zone/object-zone.v1.json",
"visibility": "public",
"visibility": "public",
"compatibility": "backward",
"compatibility": "backward",
- "status": "planned",
+ "status": "active",
"adr": "ADR-031"
"adr": "ADR-031"
},
},
{
{
@@ -5979,7 +5979,7 @@
@@ -5979,7 +5979,7 @@
"schema": "zone/object-zone-create-request.v1.json",
"schema": "zone/object-zone-create-request.v1.json",
"visibility": "public",
"visibility": "public",
"compatibility": "backward",
"compatibility": "backward",
- "status": "planned",
+ "status": "active",
"adr": "ADR-031"
"adr": "ADR-031"
},
},
{
{
@@ -5989,7 +5989,7 @@
@@ -5989,7 +5989,7 @@
"schema": "zone/object-zone-update-request.v1.json",
"schema": "zone/object-zone-update-request.v1.json",
"visibility": "public",
"visibility": "public",
"compatibility": "backward",
"compatibility": "backward",
- "status": "planned",
+ "status": "active",
"adr": "ADR-031"
"adr": "ADR-031"
},
},
{
{
@@ -5999,7 +5999,7 @@
@@ -5999,7 +5999,7 @@
"schema": "zone/object-zone-list-query.v1.json",
"schema": "zone/object-zone-list-query.v1.json",
"visibility": "public",
"visibility": "public",
"compatibility": "backward",
"compatibility": "backward",
- "status": "planned",
+ "status": "active",
"adr": "ADR-031"
"adr": "ADR-031"
},
},
{
{
@@ -6009,7 +6009,7 @@
@@ -6009,7 +6009,7 @@
"schema": "zone/object-zone-list.v1.json",
"schema": "zone/object-zone-list.v1.json",
"visibility": "public",
"visibility": "public",
"compatibility": "backward",
"compatibility": "backward",
- "status": "planned",
+ "status": "active",
"adr": "ADR-031"
"adr": "ADR-031"
},
},
{
{
@@ -6019,7 +6019,7 @@
@@ -6019,7 +6019,7 @@
"schema": "zone/object-zone-delete-query.v1.json",
"schema": "zone/object-zone-delete-query.v1.json",
"visibility": "public",
"visibility": "public",
"compatibility": "backward",
"compatibility": "backward",
- "status": "planned",
+ "status": "active",
"adr": "ADR-031"
"adr": "ADR-031"
},
},
{
{
@@ -6029,7 +6029,7 @@
@@ -6029,7 +6029,7 @@
"schema": "zone/object-zone-usage.v1.json",
"schema": "zone/object-zone-usage.v1.json",
"visibility": "public",
"visibility": "public",
"compatibility": "backward",
"compatibility": "backward",
- "status": "planned",
+ "status": "active",
"adr": "ADR-031"
"adr": "ADR-031"
},
},
{
{
@@ -7205,6 +7205,46 @@
@@ -7205,6 +7205,46 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active",
"status": "active",
"adr": "Games ADR-0007"
"adr": "Games ADR-0007"
+ },
+ {
+ "id": "media.access-key",
+ "version": "1.0.0",
+ "owner": "media",
+ "schema": "media/access-key.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-031"
+ },
+ {
+ "id": "media.access-key-issue-request",
+ "version": "1.0.0",
+ "owner": "media",
+ "schema": "media/access-key-issue-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-031"
+ },
+ {
+ "id": "media.access-key-issued",
+ "version": "1.0.0",
+ "owner": "media",
+ "schema": "media/access-key-issued.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-031"
+ },
+ {
+ "id": "media.access-key-list",
+ "version": "1.0.0",
+ "owner": "media",
+ "schema": "media/access-key-list.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-031"
}
}
]
]
}
}
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/media/access-key-issue-request.v1.json",
+ "title": "AccessKeyIssueRequest",
+ "description": "media.access-key-issue-request@1 (ADR-031): the body of POST /api/v1/projects/:project_id/access-keys on OpenVibe.Media (capability media.access_key.manage), sent by a first-party service (OpenVibe.Zone, OpenVibe.Services) for the person in on_behalf_of, who must be an admin or owner of the project (Network decides). The answer is 201 media.access-key-issued@1. A zone that is not an active zone of this project environment answers 404 media.access_key.unknown_zone; more than 50 live keys in one environment answer 409 media.access_key.limit.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["env", "name", "on_behalf_of"],
+ "properties": {
+ "env": { "enum": ["sandbox", "production"] },
+ "name": { "type": "string", "minLength": 1, "maxLength": 80 },
+ "permissions": { "enum": ["read", "write", "read-write"], "default": "read-write" },
+ "zones": { "type": "array", "maxItems": 20, "uniqueItems": true, "items": { "type": "string", "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$" } },
+ "on_behalf_of": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/media/access-key-issued.v1.json",
+ "title": "AccessKeyIssued",
+ "description": "media.access-key-issued@1 (ADR-031): the 201 of POST /api/v1/projects/:project_id/access-keys. `secret` is the S3 secret access key; it is in this answer only, and Media never shows it again. A caller relays it to the person once and keeps no copy.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["key", "secret"],
+ "properties": {
+ "key": { "$ref": "access-key.v1.json" },
+ "secret": { "type": "string", "pattern": "^[A-Za-z0-9_-]{40}$" }
+ }
+}
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/media/access-key-list.v1.json",
+ "title": "AccessKeyList",
+ "description": "media.access-key-list@1 (ADR-031): GET /api/v1/projects/:project_id/access-keys (capability media.access_key.manage, for a project admin in on_behalf_of): every key of the project, newest first, revoked ones included. No secret is ever listed.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["keys"],
+ "properties": {
+ "keys": { "type": "array", "maxItems": 500, "items": { "$ref": "access-key.v1.json" } }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/media/access-key.v1.json",
+ "title": "AccessKey",
+ "description": "media.access-key@1 (ADR-031, amended 2026-10-14): one S3 access key of OpenVibe.Media's S3 surface (https://s3.openvibe.media). It is bound to one project environment, reads and/or writes, and may be scoped to some zones of that environment (zone.object-zone@1 ids; empty means every zone). Media issues and verifies it: SigV4 needs the secret, so the party that checks signatures keeps it, encrypted, and shows it once (media.access-key-issued@1). The secret is never part of this record. A revoked key is refused at once; a key scoped only to a zone that is deleted is revoked with it. More fields may be added.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["id", "project_id", "env", "name", "permissions", "zones", "created_by", "created_at", "last_used_at", "revoked_at"],
+ "properties": {
+ "id": { "type": "string", "pattern": "^ovk_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The S3 access key id." },
+ "project_id": { "type": "string", "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "env": { "enum": ["sandbox", "production"] },
+ "name": { "type": "string", "minLength": 1, "maxLength": 80, "description": "What uses the key, as the person who issued it named it." },
+ "permissions": { "enum": ["read", "write", "read-write"] },
+ "zones": { "type": "array", "maxItems": 20, "uniqueItems": true, "items": { "type": "string", "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$" }, "description": "The zones it may reach; empty = every zone of the environment." },
+ "created_by": { "type": "string", "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "created_at": { "type": "string", "format": "date-time" },
+ "last_used_at": { "type": ["string", "null"], "format": "date-time", "description": "The last signed request, to the minute." },
+ "revoked_at": { "type": ["string", "null"], "format": "date-time" }
+ }
+}
@@ -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/zone/object-zone-create-request.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone-create-request.v1.json",
"title": "ObjectZoneCreateRequest",
"title": "ObjectZoneCreateRequest",
- "description": "zone.object-zone-create-request@1 (PLANNED; ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.",
+ "description": "zone.object-zone-create-request@1 (ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"required": ["env", "name"],
"required": ["env", "name"],
@@ -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/zone/object-zone-delete-query.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone-delete-query.v1.json",
"title": "ObjectZoneDeleteQuery",
"title": "ObjectZoneDeleteQuery",
- "description": "zone.object-zone-delete-query@1 (PLANNED; ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.",
+ "description": "zone.object-zone-delete-query@1 (ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"properties": {
"properties": {
@@ -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/zone/object-zone-list-query.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone-list-query.v1.json",
"title": "ObjectZoneListQuery",
"title": "ObjectZoneListQuery",
- "description": "zone.object-zone-list-query@1 (PLANNED; ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.",
+ "description": "zone.object-zone-list-query@1 (ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"properties": {
"properties": {
@@ -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/zone/object-zone-list.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone-list.v1.json",
"title": "ObjectZoneList",
"title": "ObjectZoneList",
- "description": "zone.object-zone-list@1 (PLANNED; ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.",
+ "description": "zone.object-zone-list@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"required": [
"required": [
@@ -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/zone/object-zone-update-request.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone-update-request.v1.json",
"title": "ObjectZoneUpdateRequest",
"title": "ObjectZoneUpdateRequest",
- "description": "zone.object-zone-update-request@1 (PLANNED; ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.",
+ "description": "zone.object-zone-update-request@1 (ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"minProperties": 1,
"minProperties": 1,
@@ -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/zone/object-zone-usage.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone-usage.v1.json",
"title": "ObjectZoneUsage",
"title": "ObjectZoneUsage",
- "description": "zone.object-zone-usage@1 (PLANNED; ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.",
+ "description": "zone.object-zone-usage@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"required": [
"required": [
@@ -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/zone/object-zone.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone.v1.json",
"title": "ObjectZone",
"title": "ObjectZone",
- "description": "zone.object-zone@1 (PLANNED; ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.",
+ "description": "zone.object-zone@1 (ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"required": ["id", "ovrn", "project_id", "env", "name", "default", "access", "state", "media_namespace", "quota_bytes", "revision", "created_at"],
"required": ["id", "ovrn", "project_id", "env", "name", "default", "access", "state", "media_namespace", "quota_bytes", "revision", "created_at"],
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
# ADR-031: An S3-compatible surface for OpenVibe.Media
# ADR-031: An S3-compatible surface for OpenVibe.Media
-**Status:** Accepted 2026-09-26 (roadmap WS-N task 8). Implementation follows in OpenVibe.Media. Amended 2026-10-02: named object zones (OpenVibe.Zone). A bucket is a named zone inside a project environment, resolved per access key. The environment's own namespace is the zone `default`.
+**Status:** Accepted 2026-09-26 (roadmap WS-N task 8). Implemented 2026-10-14 (contracts 0.173.0): Media's S3 surface and OpenVibe.Zone. Amended 2026-10-02: named object zones (OpenVibe.Zone). Amended 2026-10-14: Media issues the access keys. A bucket is a named zone inside a project environment, resolved per access key. The environment's own namespace is the zone `default`.
## Context and current evidence
## Context and current evidence
@@ -276,3 +276,24 @@ A hold placed on an object of the zone after that check is still allowed: Media
@@ -276,3 +276,24 @@ A hold placed on an object of the zone after that check is still allowed: Media
- **Identity:** `contracts.zones.checkObjectZone` refuses a record whose OVRN or namespace names another zone or project. Media refuses such a provision call.
- **Identity:** `contracts.zones.checkObjectZone` refuses a record whose OVRN or namespace names another zone or project. Media refuses such a provision call.
- **Usage:** every Media usage rollup carries the zone's resource name as `resource` and its id as `dimension`. Their stored-byte total matches Media's live bytes for the zone.
- **Usage:** every Media usage rollup carries the zone's resource name as `resource` and its id as `dimension`. Their stored-byte total matches Media's live bytes for the zone.
- **Reconciliation:** an injected mismatch between Zone and Media's projection raises the alert.
- **Reconciliation:** an injected mismatch between Zone and Media's projection raises the alert.
+
+## Amendment 2026-10-14: Media issues the access keys; hosts until DNS
+
+**Status:** Accepted and implemented (contracts 0.173.0, OpenVibe.Media, OpenVibe.Zone).
+
+**Keys move from Codes to Media.** The Decision above gave key issuance to Codes. Since 2026-10-08 Codes is the coding harness, and the developer console moved to Services, which aggregates authorities and owns none of their records (D33). SigV4 also needs the secret wherever signatures are checked, and that is Media. If Codes kept the keys, every request would carry a secret across services.
+- **Media issues, stores and verifies the keys.** `media_access_keys` holds an `ovk_<ULID>` id, the project, environment, permissions (`read`, `write`, `read-write`), an optional zone list, and the secret encrypted with AES-256-GCM under `MEDIA_S3_KEY_SECRET`.
+- **The secret is shown once,** in `media.access-key-issued@1`.
+- **Keys are managed through Media's API** (capability `media.access_key.manage`). Zone's console and Services call it for a person who administers the project. Media asks Network for that person's role, as its control API does.
+- **When a zone is deleted,** a key scoped only to that zone is revoked, and other keys drop the zone from their scope.
+
+**Hosts.** The S3 surface is served by `Host` (`MEDIA_S3_HOSTS`, `s3.openvibe.media`), not by a path prefix.
+- nginx passes the request line untouched, because the client signed that exact path. An nginx `proxy_pass` with a URI part would decode it.
+- Zone serves its site and API on its apex, `https://openvibe.zone`.
+- `s3.openvibe.media` and `api.openvibe.zone` wait for their DNS records. The DNS token expired, so creating them is the owner's action.
+- The certificates already cover both names (wildcards).
+
+**What v1 leaves out.**
+- Multipart uploads and CopyObject answer `NotImplemented`. Single-request objects go up to Media's single-upload limit.
+- Overwritten and deleted objects keep counting against a developer project's quota until they are purged. That is Media's existing rule for developer tenants; this ADR's "only live objects count" applies once Media changes that rule.
+- Per-zone usage rollups (`media.usage.recorded` with the zone's resource name) are not emitted yet. Zone shows Media's live snapshot.
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "env": "staging",
+ "name": "backups",
+ "on_behalf_of": "usr_01JZ0000000000000000000A01"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
+{
+ "env": "production",
+ "name": "backups",
+ "permissions": "write",
+ "zones": [
+ "zon_01JZ0000000000000000000Z01"
+ ],
+ "on_behalf_of": "usr_01JZ0000000000000000000A01"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
+{
+ "key": {
+ "id": "ovk_01JZ0000000000000000000K01",
+ "project_id": "prj_01JZ0000000000000000000P01",
+ "env": "production",
+ "name": "ci deploys",
+ "permissions": "read-write",
+ "zones": [],
+ "created_by": "usr_01JZ0000000000000000000A01",
+ "created_at": "2026-10-14T12:00:00.000Z",
+ "last_used_at": null,
+ "revoked_at": null
+ },
+ "secret": "short"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
+{
+ "key": {
+ "id": "ovk_01JZ0000000000000000000K01",
+ "project_id": "prj_01JZ0000000000000000000P01",
+ "env": "production",
+ "name": "ci deploys",
+ "permissions": "read-write",
+ "zones": [],
+ "created_by": "usr_01JZ0000000000000000000A01",
+ "created_at": "2026-10-14T12:00:00.000Z",
+ "last_used_at": null,
+ "revoked_at": null
+ },
+ "secret": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "keys": [
+ {
+ "id": "ovk_01JZ0000000000000000000K01",
+ "secret": "nope"
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+{
+ "keys": [
+ {
+ "id": "ovk_01JZ0000000000000000000K01",
+ "project_id": "prj_01JZ0000000000000000000P01",
+ "env": "production",
+ "name": "ci deploys",
+ "permissions": "read-write",
+ "zones": [],
+ "created_by": "usr_01JZ0000000000000000000A01",
+ "created_at": "2026-10-14T12:00:00.000Z",
+ "last_used_at": null,
+ "revoked_at": null
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "id": "ovk_01JZ0000000000000000000K01",
+ "project_id": "prj_01JZ0000000000000000000P01",
+ "env": "production",
+ "name": "ci deploys",
+ "permissions": "read-write",
+ "zones": [],
+ "created_by": "usr_01JZ0000000000000000000A01",
+ "created_at": "2026-10-14T12:00:00.000Z",
+ "last_used_at": null,
+ "revoked_at": null,
+ "secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
+{
+ "id": "ovk_01JZ0000000000000000000K01",
+ "project_id": "prj_01JZ0000000000000000000P01",
+ "env": "production",
+ "name": "ci deploys",
+ "permissions": "read-write",
+ "zones": [],
+ "created_by": "usr_01JZ0000000000000000000A01",
+ "created_at": "2026-10-14T12:00:00.000Z",
+ "last_used_at": null,
+ "revoked_at": null
+}
@@ -68568,12 +68568,12 @@
@@ -68568,12 +68568,12 @@
"zone.object-zone": {
"zone.object-zone": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "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/zone/object-zone.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone.v1.json",
"title": "ObjectZone",
"title": "ObjectZone",
- "description": "zone.object-zone@1 (PLANNED; ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.",
+ "description": "zone.object-zone@1 (ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"required": [
"required": [
@@ -68862,12 +68862,12 @@
@@ -68862,12 +68862,12 @@
"zone.object-zone-create-request": {
"zone.object-zone-create-request": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "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/zone/object-zone-create-request.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone-create-request.v1.json",
"title": "ObjectZoneCreateRequest",
"title": "ObjectZoneCreateRequest",
- "description": "zone.object-zone-create-request@1 (PLANNED; ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.",
+ "description": "zone.object-zone-create-request@1 (ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"required": [
"required": [
@@ -68910,12 +68910,12 @@
@@ -68910,12 +68910,12 @@
"zone.object-zone-update-request": {
"zone.object-zone-update-request": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "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/zone/object-zone-update-request.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone-update-request.v1.json",
"title": "ObjectZoneUpdateRequest",
"title": "ObjectZoneUpdateRequest",
- "description": "zone.object-zone-update-request@1 (PLANNED; ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.",
+ "description": "zone.object-zone-update-request@1 (ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"minProperties": 1,
"minProperties": 1,
@@ -68941,12 +68941,12 @@
@@ -68941,12 +68941,12 @@
"zone.object-zone-list-query": {
"zone.object-zone-list-query": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "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/zone/object-zone-list-query.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone-list-query.v1.json",
"title": "ObjectZoneListQuery",
"title": "ObjectZoneListQuery",
- "description": "zone.object-zone-list-query@1 (PLANNED; ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.",
+ "description": "zone.object-zone-list-query@1 (ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"properties": {
"properties": {
@@ -68990,12 +68990,12 @@
@@ -68990,12 +68990,12 @@
"zone.object-zone-list": {
"zone.object-zone-list": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "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/zone/object-zone-list.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone-list.v1.json",
"title": "ObjectZoneList",
"title": "ObjectZoneList",
- "description": "zone.object-zone-list@1 (PLANNED; ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.",
+ "description": "zone.object-zone-list@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"required": [
"required": [
@@ -69024,12 +69024,12 @@
@@ -69024,12 +69024,12 @@
"zone.object-zone-delete-query": {
"zone.object-zone-delete-query": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "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/zone/object-zone-delete-query.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone-delete-query.v1.json",
"title": "ObjectZoneDeleteQuery",
"title": "ObjectZoneDeleteQuery",
- "description": "zone.object-zone-delete-query@1 (PLANNED; ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.",
+ "description": "zone.object-zone-delete-query@1 (ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"properties": {
"properties": {
@@ -69048,12 +69048,12 @@
@@ -69048,12 +69048,12 @@
"zone.object-zone-usage": {
"zone.object-zone-usage": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "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/zone/object-zone-usage.v1.json",
"$id": "https://openvibe.network/contracts/zone/object-zone-usage.v1.json",
"title": "ObjectZoneUsage",
"title": "ObjectZoneUsage",
- "description": "zone.object-zone-usage@1 (PLANNED; ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.",
+ "description": "zone.object-zone-usage@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.",
"type": "object",
"type": "object",
"additionalProperties": false,
"additionalProperties": false,
"required": [
"required": [
@@ -80753,6 +80753,197 @@
@@ -80753,6 +80753,197 @@
}
}
}
}
}
}
+ },
+ "media.access-key": {
+ "version": "1.0.0",
+ "owner": "media",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/media/access-key.v1.json",
+ "title": "AccessKey",
+ "description": "media.access-key@1 (ADR-031, amended 2026-10-14): one S3 access key of OpenVibe.Media's S3 surface (https://s3.openvibe.media). It is bound to one project environment, reads and/or writes, and may be scoped to some zones of that environment (zone.object-zone@1 ids; empty means every zone). Media issues and verifies it: SigV4 needs the secret, so the party that checks signatures keeps it, encrypted, and shows it once (media.access-key-issued@1). The secret is never part of this record. A revoked key is refused at once; a key scoped only to a zone that is deleted is revoked with it. More fields may be added.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "project_id",
+ "env",
+ "name",
+ "permissions",
+ "zones",
+ "created_by",
+ "created_at",
+ "last_used_at",
+ "revoked_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^ovk_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The S3 access key id."
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "env": {
+ "enum": [
+ "sandbox",
+ "production"
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80,
+ "description": "What uses the key, as the person who issued it named it."
+ },
+ "permissions": {
+ "enum": [
+ "read",
+ "write",
+ "read-write"
+ ]
+ },
+ "zones": {
+ "type": "array",
+ "maxItems": 20,
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "description": "The zones it may reach; empty = every zone of the environment."
+ },
+ "created_by": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "last_used_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "The last signed request, to the minute."
+ },
+ "revoked_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "media.access-key-issue-request": {
+ "version": "1.0.0",
+ "owner": "media",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/media/access-key-issue-request.v1.json",
+ "title": "AccessKeyIssueRequest",
+ "description": "media.access-key-issue-request@1 (ADR-031): the body of POST /api/v1/projects/:project_id/access-keys on OpenVibe.Media (capability media.access_key.manage), sent by a first-party service (OpenVibe.Zone, OpenVibe.Services) for the person in on_behalf_of, who must be an admin or owner of the project (Network decides). The answer is 201 media.access-key-issued@1. A zone that is not an active zone of this project environment answers 404 media.access_key.unknown_zone; more than 50 live keys in one environment answer 409 media.access_key.limit.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "env",
+ "name",
+ "on_behalf_of"
+ ],
+ "properties": {
+ "env": {
+ "enum": [
+ "sandbox",
+ "production"
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ },
+ "permissions": {
+ "enum": [
+ "read",
+ "write",
+ "read-write"
+ ],
+ "default": "read-write"
+ },
+ "zones": {
+ "type": "array",
+ "maxItems": 20,
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ },
+ "on_behalf_of": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+ }
+ },
+ "media.access-key-issued": {
+ "version": "1.0.0",
+ "owner": "media",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/media/access-key-issued.v1.json",
+ "title": "AccessKeyIssued",
+ "description": "media.access-key-issued@1 (ADR-031): the 201 of POST /api/v1/projects/:project_id/access-keys. `secret` is the S3 secret access key; it is in this answer only, and Media never shows it again. A caller relays it to the person once and keeps no copy.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "secret"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "access-key.v1.json"
+ },
+ "secret": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_-]{40}$"
+ }
+ }
+ }
+ },
+ "media.access-key-list": {
+ "version": "1.0.0",
+ "owner": "media",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/media/access-key-list.v1.json",
+ "title": "AccessKeyList",
+ "description": "media.access-key-list@1 (ADR-031): GET /api/v1/projects/:project_id/access-keys (capability media.access_key.manage, for a project admin in on_behalf_of): every key of the project, newest first, revoked ones included. No secret is ever listed.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "keys"
+ ],
+ "properties": {
+ "keys": {
+ "type": "array",
+ "maxItems": 500,
+ "items": {
+ "$ref": "access-key.v1.json"
+ }
+ }
+ }
+ }
}
}
}
}
}
}
@@ -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.172.2",
- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.Chat API",
"title": "OpenVibe.Chat API",
- "version": "0.172.2",
- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.173.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"
@@ -117,8 +117,8 @@
@@ -117,8 +117,8 @@
"service": "media",
"service": "media",
"name": "OpenVibe.Media",
"name": "OpenVibe.Media",
"origin": "https://openvibe.media",
"origin": "https://openvibe.media",
- "operations": 10,
- "capabilities": 7
+ "operations": 15,
+ "capabilities": 10
},
},
{
{
"service": "media-hub",
"service": "media-hub",
@@ -245,6 +245,13 @@
@@ -245,6 +245,13 @@
"origin": "https://openvibe.work",
"origin": "https://openvibe.work",
"operations": 5,
"operations": 5,
"capabilities": 1
"capabilities": 1
+ },
+ {
+ "service": "zone",
+ "name": "OpenVibe.Zone",
+ "origin": "https://openvibe.zone",
+ "operations": 9,
+ "capabilities": 8
}
}
]
]
}
}
@@ -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.172.2",
- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.173.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"
@@ -146,6 +146,310 @@
@@ -146,6 +146,310 @@
}
}
}
}
},
},
+ "/api/v1/projects/{project_id}/access-keys": {
+ "get": {
+ "operationId": "get_api_v1_projects_project_id_access_keys",
+ "summary": "Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies).",
+ "description": "**media.access_key.manage** (first-party): Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies). A first-party service (OpenVibe.Zone, OpenVibe.Services) calls for the person in on_behalf_of, who must be an admin or owner of the project (Media asks Network, network.project.member.read). POST /api/v1/projects/:project_id/access-keys takes media.access-key-issue-request@1 and answers 201 media.access-key-issued@1, the only answer that carries the secret; GET lists media.access-key-list@1; DELETE /:key_id revokes at once. Loopback only.",
+ "tags": [
+ "media.access_key"
+ ],
+ "x-openvibe-capabilities": [
+ "media.access_key.manage"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "internal-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "media.access_key.manage"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/media.access-key-issue-request.v1"
+ },
+ "parameters": [
+ {
+ "name": "project_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "env",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "enum": [
+ "sandbox",
+ "production"
+ ]
+ }
+ },
+ {
+ "name": "name",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ }
+ },
+ {
+ "name": "permissions",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "enum": [
+ "read",
+ "write",
+ "read-write"
+ ],
+ "default": "read-write"
+ }
+ },
+ {
+ "name": "zones",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "array",
+ "maxItems": 20,
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+ },
+ {
+ "name": "on_behalf_of",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/media.access-key-issued.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "operationId": "post_api_v1_projects_project_id_access_keys",
+ "summary": "Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies).",
+ "description": "**media.access_key.manage** (first-party): Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies). A first-party service (OpenVibe.Zone, OpenVibe.Services) calls for the person in on_behalf_of, who must be an admin or owner of the project (Media asks Network, network.project.member.read). POST /api/v1/projects/:project_id/access-keys takes media.access-key-issue-request@1 and answers 201 media.access-key-issued@1, the only answer that carries the secret; GET lists media.access-key-list@1; DELETE /:key_id revokes at once. Loopback only.",
+ "tags": [
+ "media.access_key"
+ ],
+ "x-openvibe-capabilities": [
+ "media.access_key.manage"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "internal-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "media.access_key.manage"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/media.access-key-issue-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "project_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/media.access-key-issued.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/projects/{project_id}/access-keys/{key_id}": {
+ "delete": {
+ "operationId": "delete_api_v1_projects_project_id_access_keys_key_id",
+ "summary": "Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies).",
+ "description": "**media.access_key.manage** (first-party): Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies). A first-party service (OpenVibe.Zone, OpenVibe.Services) calls for the person in on_behalf_of, who must be an admin or owner of the project (Media asks Network, network.project.member.read). POST /api/v1/projects/:project_id/access-keys takes media.access-key-issue-request@1 and answers 201 media.access-key-issued@1, the only answer that carries the secret; GET lists media.access-key-list@1; DELETE /:key_id revokes at once. Loopback only.",
+ "tags": [
+ "media.access_key"
+ ],
+ "x-openvibe-capabilities": [
+ "media.access_key.manage"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "internal-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "media.access_key.manage"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/media.access-key-issue-request.v1"
+ },
+ "parameters": [
+ {
+ "name": "project_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "key_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "env",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "enum": [
+ "sandbox",
+ "production"
+ ]
+ }
+ },
+ {
+ "name": "name",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ }
+ },
+ {
+ "name": "permissions",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "enum": [
+ "read",
+ "write",
+ "read-write"
+ ],
+ "default": "read-write"
+ }
+ },
+ {
+ "name": "zones",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "array",
+ "maxItems": 20,
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+ },
+ {
+ "name": "on_behalf_of",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/media.access-key-issued.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",
@@ -599,50 +903,391 @@
@@ -599,50 +903,391 @@
}
}
}
}
}
}
- }
- },
- "components": {
- "schemas": {
- "common.resource-control-request.v1": {
- "title": "ResourceControlRequest",
- "description": "common.resource-control-request@1: one control operation OpenVibe.Services (or Actor's Console) sends to the authority that owns a resource (ADR-048, plan T13). Services never mutates another service's database; every create, update, start, stop, delete, resize, rotate, pair, grant or revoke is this request to the owning authority's control API, which decides and answers with a common.resource-control-result@1. A request names exactly one of resource (every action but create) or resource_kind (create, before the name exists). project_id is the tenancy boundary and equals the project segment of resource. idempotency_key makes a retry safe: the authority answers a repeated key with the first result. A sensitive action (money, publishing, deleting, physical control) is refused with confirmation_required until its owner approves; the retry carries that confirmation_id (roadmap WS-Z2). The cross-field rules are checked by contracts.resources.checkControlRequest.",
- "type": "object",
- "additionalProperties": false,
- "required": [
- "action",
- "project_id",
- "idempotency_key"
+ },
+ "/internal/zones/{zone_id}": {
+ "get": {
+ "operationId": "get_internal_zones_zone_id",
+ "summary": "Provision a zone in OpenVibe.Media (ADR-031 amendment 2026-10-02), granted only to OpenVibe.Zone: PUT /internal/zones/:zone_id with the zone.object-zone@1 record Zone owns.",
+ "description": "**media.zone.provision** (first-party): Provision a zone in OpenVibe.Media (ADR-031 amendment 2026-10-02), granted only to OpenVibe.Zone: PUT /internal/zones/:zone_id with the zone.object-zone@1 record Zone owns. Media applies a record only when its revision is higher than the one it holds, checks it with contracts.zones.checkObjectZone, and refuses a name another zone of the environment holds (409 media.zone.name_retired) and any change of project, environment, name or namespace (409 media.zone.immutable). Media keeps a projection of each zone to resolve S3 bucket names, enforce the zone's quota_bytes beside the environment's, and answer NoSuchBucket for a zone that is deleting or deleted. GET /internal/zones/:zone_id reports the live objects and, while the zone is deleting, the objects remaining and held; Media tombstones and purges a deleting zone's objects (no restore window) and records when none remain. Loopback only.",
+ "tags": [
+ "media.zone"
],
],
- "oneOf": [
- {
- "required": [
- "resource"
- ]
- },
+ "x-openvibe-capabilities": [
+ "media.zone.provision"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "internal-write",
+ "security": [
{
{
- "required": [
- "resource_kind"
+ "openvibeToken": [
+ "media.zone.provision"
]
]
}
}
],
],
- "properties": {
- "action": {
- "enum": [
- "create",
- "update",
- "delete",
- "start",
- "stop",
- "suspend",
- "resume",
- "resize",
- "rotate",
- "pair",
- "grant",
- "revoke",
- "archive"
- ]
- },
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/zone.object-zone.v1"
+ },
+ "parameters": [
+ {
+ "name": "zone_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "id",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ },
+ {
+ "name": "ovrn",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^ovrn:zone:prj_[0-9A-HJKMNP-TV-Z]{26}:object-zone/zon_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The resource name used in grants, audit, events, usage records and bills (ADR-034 section 2)."
+ },
+ "description": "The resource name used in grants, audit, events, usage records and bills (ADR-034 section 2)."
+ },
+ {
+ "name": "project_id",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ },
+ {
+ "name": "env",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "enum": [
+ "sandbox",
+ "production"
+ ],
+ "description": "Fixed at creation. A sandbox key never resolves a production zone."
+ },
+ "description": "Fixed at creation. A sandbox key never resolves a production zone."
+ },
+ {
+ "name": "name",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 63,
+ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
+ "description": "The S3 bucket name. It is unique among all the zones of one project environment, deleted ones included: a deleted zone's name is retired, never given to another zone. It is valid as an S3 bucket name."
+ },
+ "description": "The S3 bucket name. It is unique among all the zones of one project environment, deleted ones included: a deleted zone's name is retired, never given to another zone. It is valid as an S3 bucket name."
+ },
+ {
+ "name": "default",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "boolean",
+ "description": "The environment's default zone, named `default`. Its media_namespace is the environment root."
+ },
+ "description": "The environment's default zone, named `default`. Its media_namespace is the environment root."
+ },
+ {
+ "name": "description",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 200
+ }
+ },
+ {
+ "name": "access",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "enum": [
+ "private",
+ "per-object"
+ ],
+ "description": "private: every named zone in v1. Its objects never have a public_url and never enter a public cache. per-object: only the default zone, whose existing objects keep their own visibility (public, unlisted or private), public URLs and cached copies, as media.object@1 allows. The S3 surface never serves an object without a signature, in either kind of zone. Publication of a named zone would add a value in a later minor version."
+ },
+ "description": "private: every named zone in v1. Its objects never have a public_url and never enter a public cache. per-object: only the default zone, whose existing objects keep their own visibility (public, unlisted or private), public URLs and cached copies, as media.object@1 allows. The S3 surface never serves an object without a signature, in either kind of zone. Publication of a named zone would add a value in a later minor version."
+ },
+ {
+ "name": "state",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "enum": [
+ "provisioning",
+ "active",
+ "deleting",
+ "deleted"
+ ],
+ "description": "provisioning: Media has not yet acknowledged the zone. active: it serves requests. deleting: requests answer NoSuchBucket while Media tombstones the objects and erases every copy. deleted: no object or copy remains. The record is kept, and its name stays retired."
+ },
+ "description": "provisioning: Media has not yet acknowledged the zone. active: it serves requests. deleting: requests answer NoSuchBucket while Media tombstones the objects and erases every copy. deleted: no object or copy remains. The record is kept, and its name stays retired."
+ },
+ {
+ "name": "media_namespace",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}(\\.sandbox)?(\\.zon_[0-9A-HJKMNP-TV-Z]{26})?$",
+ "description": "Where Media keeps the zone's objects. It is the environment root for the default zone, and <root>.<id> for every other zone."
+ },
+ "description": "Where Media keeps the zone's objects. It is the environment root for the default zone, and <root>.<id> for every other zone."
+ },
+ {
+ "name": "quota_bytes",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "minimum": 1,
+ "description": "An optional byte ceiling for this zone. Media enforces it together with the project environment's byte quota, and a write over either is refused with QuotaExceeded. null means only the environment quota applies."
+ },
+ "description": "An optional byte ceiling for this zone. Media enforces it together with the project environment's byte quota, and a write over either is refused with QuotaExceeded. null means only the environment quota applies."
+ },
+ {
+ "name": "usage",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "stored_bytes",
+ "objects",
+ "as_of"
+ ],
+ "description": "A snapshot of the live objects, as Media reports it. Zone never counts. Billed usage comes from Media's hourly media.usage.recorded rollups, whose dimension is this id in lowercase.",
+ "properties": {
+ "stored_bytes": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "objects": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "as_of": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "description": "A snapshot of the live objects, as Media reports it. Zone never counts. Billed usage comes from Media's hourly media.usage.recorded rollups, whose dimension is this id in lowercase."
+ },
+ {
+ "name": "deletion",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "requested_at",
+ "objects_remaining",
+ "held_objects",
+ "completed_at"
+ ],
+ "description": "Progress of a zone deletion, as Media reports it.",
+ "properties": {
+ "requested_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "objects_remaining": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Objects not yet tombstoned, plus tombstoned objects with a copy not yet confirmed erased."
+ },
+ "held_objects": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Objects whose erasure waits for a retention hold (ADR-006). They are already unreadable."
+ },
+ "completed_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ }
+ },
+ "description": "Progress of a zone deletion, as Media reports it."
+ },
+ {
+ "name": "revision",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Raised by every change. Zone sends it when it provisions the change in Media, and Media applies only a higher revision."
+ },
+ "description": "Raised by every change. Zone sends it when it provisions the change in Media, and Media applies only a higher revision."
+ },
+ {
+ "name": "created_at",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "date-time"
+ }
+ },
+ {
+ "name": "updated_at",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success"
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ },
+ "put": {
+ "operationId": "put_internal_zones_zone_id",
+ "summary": "Provision a zone in OpenVibe.Media (ADR-031 amendment 2026-10-02), granted only to OpenVibe.Zone: PUT /internal/zones/:zone_id with the zone.object-zone@1 record Zone owns.",
+ "description": "**media.zone.provision** (first-party): Provision a zone in OpenVibe.Media (ADR-031 amendment 2026-10-02), granted only to OpenVibe.Zone: PUT /internal/zones/:zone_id with the zone.object-zone@1 record Zone owns. Media applies a record only when its revision is higher than the one it holds, checks it with contracts.zones.checkObjectZone, and refuses a name another zone of the environment holds (409 media.zone.name_retired) and any change of project, environment, name or namespace (409 media.zone.immutable). Media keeps a projection of each zone to resolve S3 bucket names, enforce the zone's quota_bytes beside the environment's, and answer NoSuchBucket for a zone that is deleting or deleted. GET /internal/zones/:zone_id reports the live objects and, while the zone is deleting, the objects remaining and held; Media tombstones and purges a deleting zone's objects (no restore window) and records when none remain. Loopback only.",
+ "tags": [
+ "media.zone"
+ ],
+ "x-openvibe-capabilities": [
+ "media.zone.provision"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "internal-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "media.zone.provision"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/zone.object-zone.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "zone_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success"
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "common.resource-control-request.v1": {
+ "title": "ResourceControlRequest",
+ "description": "common.resource-control-request@1: one control operation OpenVibe.Services (or Actor's Console) sends to the authority that owns a resource (ADR-048, plan T13). Services never mutates another service's database; every create, update, start, stop, delete, resize, rotate, pair, grant or revoke is this request to the owning authority's control API, which decides and answers with a common.resource-control-result@1. A request names exactly one of resource (every action but create) or resource_kind (create, before the name exists). project_id is the tenancy boundary and equals the project segment of resource. idempotency_key makes a retry safe: the authority answers a repeated key with the first result. A sensitive action (money, publishing, deleting, physical control) is refused with confirmation_required until its owner approves; the retry carries that confirmation_id (roadmap WS-Z2). The cross-field rules are checked by contracts.resources.checkControlRequest.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "action",
+ "project_id",
+ "idempotency_key"
+ ],
+ "oneOf": [
+ {
+ "required": [
+ "resource"
+ ]
+ },
+ {
+ "required": [
+ "resource_kind"
+ ]
+ }
+ ],
+ "properties": {
+ "action": {
+ "enum": [
+ "create",
+ "update",
+ "delete",
+ "start",
+ "stop",
+ "suspend",
+ "resume",
+ "resize",
+ "rotate",
+ "pair",
+ "grant",
+ "revoke",
+ "archive"
+ ]
+ },
"resource": {
"resource": {
"$ref": "#/components/schemas/common.resource-name.v1",
"$ref": "#/components/schemas/common.resource-name.v1",
"description": "The resource acted on; required for every action but create."
"description": "The resource acted on; required for every action but create."
@@ -1062,6 +1707,151 @@
@@ -1062,6 +1707,151 @@
}
}
]
]
},
},
+ "media.access-key-issue-request.v1": {
+ "title": "AccessKeyIssueRequest",
+ "description": "media.access-key-issue-request@1 (ADR-031): the body of POST /api/v1/projects/:project_id/access-keys on OpenVibe.Media (capability media.access_key.manage), sent by a first-party service (OpenVibe.Zone, OpenVibe.Services) for the person in on_behalf_of, who must be an admin or owner of the project (Network decides). The answer is 201 media.access-key-issued@1. A zone that is not an active zone of this project environment answers 404 media.access_key.unknown_zone; more than 50 live keys in one environment answer 409 media.access_key.limit.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "env",
+ "name",
+ "on_behalf_of"
+ ],
+ "properties": {
+ "env": {
+ "enum": [
+ "sandbox",
+ "production"
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80
+ },
+ "permissions": {
+ "enum": [
+ "read",
+ "write",
+ "read-write"
+ ],
+ "default": "read-write"
+ },
+ "zones": {
+ "type": "array",
+ "maxItems": 20,
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ },
+ "on_behalf_of": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+ },
+ "media.access-key-issued.v1": {
+ "title": "AccessKeyIssued",
+ "description": "media.access-key-issued@1 (ADR-031): the 201 of POST /api/v1/projects/:project_id/access-keys. `secret` is the S3 secret access key; it is in this answer only, and Media never shows it again. A caller relays it to the person once and keeps no copy.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "key",
+ "secret"
+ ],
+ "properties": {
+ "key": {
+ "$ref": "#/components/schemas/media.access-key.v1"
+ },
+ "secret": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_-]{40}$"
+ }
+ }
+ },
+ "media.access-key.v1": {
+ "title": "AccessKey",
+ "description": "media.access-key@1 (ADR-031, amended 2026-10-14): one S3 access key of OpenVibe.Media's S3 surface (https://s3.openvibe.media). It is bound to one project environment, reads and/or writes, and may be scoped to some zones of that environment (zone.object-zone@1 ids; empty means every zone). Media issues and verifies it: SigV4 needs the secret, so the party that checks signatures keeps it, encrypted, and shows it once (media.access-key-issued@1). The secret is never part of this record. A revoked key is refused at once; a key scoped only to a zone that is deleted is revoked with it. More fields may be added.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "project_id",
+ "env",
+ "name",
+ "permissions",
+ "zones",
+ "created_by",
+ "created_at",
+ "last_used_at",
+ "revoked_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^ovk_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The S3 access key id."
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "env": {
+ "enum": [
+ "sandbox",
+ "production"
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 80,
+ "description": "What uses the key, as the person who issued it named it."
+ },
+ "permissions": {
+ "enum": [
+ "read",
+ "write",
+ "read-write"
+ ]
+ },
+ "zones": {
+ "type": "array",
+ "maxItems": 20,
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "description": "The zones it may reach; empty = every zone of the environment."
+ },
+ "created_by": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "last_used_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "The last signed request, to the minute."
+ },
+ "revoked_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ }
+ },
"media.avatar-ingest-request.v1": {
"media.avatar-ingest-request.v1": {
"title": "MediaAvatarIngestRequest",
"title": "MediaAvatarIngestRequest",
"description": "media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).",
"description": "media.avatar-ingest-request@1: the body of POST /internal/avatar-ingest on OpenVibe.Media (capability media.avatar.ingest; Network calls it while adopting an avatar a person pointed at somewhere else on the web). Media fetches the URL once through its egress guard (https only, public addresses only, at most 8 MB and 10 s), re-encodes it to a 512×512 WebP and stores it as an unlisted screenshot paste owned by user_id, so no OpenVibe site ever serves or stores a third-party address. Refusals are { ok: false, error } (400 without url or user_id, 422 when the link is not a usable picture).",
@@ -1476,6 +2266,293 @@
@@ -1476,6 +2266,293 @@
]
]
}
}
}
}
+ },
+ "zone.object-zone.v1": {
+ "title": "ObjectZone",
+ "description": "zone.object-zone@1 (ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "ovrn",
+ "project_id",
+ "env",
+ "name",
+ "default",
+ "access",
+ "state",
+ "media_namespace",
+ "quota_bytes",
+ "revision",
+ "created_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "ovrn": {
+ "type": "string",
+ "pattern": "^ovrn:zone:prj_[0-9A-HJKMNP-TV-Z]{26}:object-zone/zon_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The resource name used in grants, audit, events, usage records and bills (ADR-034 section 2)."
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "env": {
+ "enum": [
+ "sandbox",
+ "production"
+ ],
+ "description": "Fixed at creation. A sandbox key never resolves a production zone."
+ },
+ "name": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 63,
+ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
+ "description": "The S3 bucket name. It is unique among all the zones of one project environment, deleted ones included: a deleted zone's name is retired, never given to another zone. It is valid as an S3 bucket name."
+ },
+ "default": {
+ "type": "boolean",
+ "description": "The environment's default zone, named `default`. Its media_namespace is the environment root."
+ },
+ "description": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 200
+ },
+ "access": {
+ "enum": [
+ "private",
+ "per-object"
+ ],
+ "description": "private: every named zone in v1. Its objects never have a public_url and never enter a public cache. per-object: only the default zone, whose existing objects keep their own visibility (public, unlisted or private), public URLs and cached copies, as media.object@1 allows. The S3 surface never serves an object without a signature, in either kind of zone. Publication of a named zone would add a value in a later minor version."
+ },
+ "state": {
+ "enum": [
+ "provisioning",
+ "active",
+ "deleting",
+ "deleted"
+ ],
+ "description": "provisioning: Media has not yet acknowledged the zone. active: it serves requests. deleting: requests answer NoSuchBucket while Media tombstones the objects and erases every copy. deleted: no object or copy remains. The record is kept, and its name stays retired."
+ },
+ "media_namespace": {
+ "type": "string",
+ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}(\\.sandbox)?(\\.zon_[0-9A-HJKMNP-TV-Z]{26})?$",
+ "description": "Where Media keeps the zone's objects. It is the environment root for the default zone, and <root>.<id> for every other zone."
+ },
+ "quota_bytes": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "minimum": 1,
+ "description": "An optional byte ceiling for this zone. Media enforces it together with the project environment's byte quota, and a write over either is refused with QuotaExceeded. null means only the environment quota applies."
+ },
+ "usage": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "stored_bytes",
+ "objects",
+ "as_of"
+ ],
+ "description": "A snapshot of the live objects, as Media reports it. Zone never counts. Billed usage comes from Media's hourly media.usage.recorded rollups, whose dimension is this id in lowercase.",
+ "properties": {
+ "stored_bytes": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "objects": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "as_of": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "deletion": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "requested_at",
+ "objects_remaining",
+ "held_objects",
+ "completed_at"
+ ],
+ "description": "Progress of a zone deletion, as Media reports it.",
+ "properties": {
+ "requested_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "objects_remaining": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Objects not yet tombstoned, plus tombstoned objects with a copy not yet confirmed erased."
+ },
+ "held_objects": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Objects whose erasure waits for a retention hold (ADR-006). They are already unreadable."
+ },
+ "completed_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ }
+ },
+ "revision": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Raised by every change. Zone sends it when it provisions the change in Media, and Media applies only a higher revision."
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updated_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "properties": {
+ "default": {
+ "const": true
+ }
+ },
+ "required": [
+ "default"
+ ]
+ },
+ "then": {
+ "properties": {
+ "name": {
+ "const": "default"
+ },
+ "access": {
+ "const": "per-object"
+ },
+ "media_namespace": {
+ "type": "string",
+ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}(\\.sandbox)?$"
+ }
+ }
+ },
+ "else": {
+ "properties": {
+ "name": {
+ "not": {
+ "const": "default"
+ }
+ },
+ "access": {
+ "const": "private"
+ },
+ "media_namespace": {
+ "type": "string",
+ "pattern": "\\.zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "env": {
+ "const": "sandbox"
+ }
+ },
+ "required": [
+ "env"
+ ]
+ },
+ "then": {
+ "properties": {
+ "media_namespace": {
+ "type": "string",
+ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}\\.sandbox(\\.|$)"
+ }
+ }
+ },
+ "else": {
+ "properties": {
+ "media_namespace": {
+ "type": "string",
+ "not": {
+ "pattern": "\\.sandbox(\\.|$)"
+ }
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "state": {
+ "enum": [
+ "deleting",
+ "deleted"
+ ]
+ }
+ },
+ "required": [
+ "state"
+ ]
+ },
+ "then": {
+ "required": [
+ "deletion"
+ ]
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "state": {
+ "const": "deleted"
+ }
+ },
+ "required": [
+ "state"
+ ]
+ },
+ "then": {
+ "properties": {
+ "deletion": {
+ "type": "object",
+ "properties": {
+ "objects_remaining": {
+ "const": 0
+ },
+ "held_objects": {
+ "const": 0
+ },
+ "completed_at": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
}
}
},
},
"securitySchemes": {
"securitySchemes": {
@@ -1486,5 +2563,39 @@
@@ -1486,5 +2563,39 @@
"description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
"description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
}
}
}
}
- }
+ },
+ "x-openvibe-other-bindings": [
+ {
+ "capability": "media.object.s3",
+ "binding": "GET s3:/"
+ },
+ {
+ "capability": "media.object.s3",
+ "binding": "HEAD s3:/:zone"
+ },
+ {
+ "capability": "media.object.s3",
+ "binding": "GET s3:/:zone"
+ },
+ {
+ "capability": "media.object.s3",
+ "binding": "POST s3:/:zone?delete"
+ },
+ {
+ "capability": "media.object.s3",
+ "binding": "PUT s3:/:zone/:key"
+ },
+ {
+ "capability": "media.object.s3",
+ "binding": "GET s3:/:zone/:key"
+ },
+ {
+ "capability": "media.object.s3",
+ "binding": "HEAD s3:/:zone/:key"
+ },
+ {
+ "capability": "media.object.s3",
+ "binding": "DELETE s3:/:zone/:key"
+ }
+ ]
}
}
@@ -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.172.2",
- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.News API",
"title": "OpenVibe.News API",
- "version": "0.172.2",
- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.173.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.172.2",
- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.172.2): 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.173.0",
+ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.173.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"
new file mode 100644
new file mode 100644
@@ -0,0 +1,1602 @@
@@ -0,0 +1,1602 @@
+{
+ "openapi": "3.1.0",
+ "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
+ "info": {
+ "title": "OpenVibe.Zone API",
+ "version": "0.173.0",
+ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.173.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"
+ }
+ },
+ "servers": [
+ {
+ "url": "https://openvibe.zone"
+ }
+ ],
+ "paths": {
+ "/api/v1/projects/{project_id}/zones": {
+ "get": {
+ "operationId": "get_api_v1_projects_project_id_zones",
+ "summary": "List a project's object zones: GET /api/v1/projects/:project_id/zones, filtered by env and paged.",
+ "description": "**zone.zone.list** (public): List a project's object zones: GET /api/v1/projects/:project_id/zones, filtered by env and paged. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",
+ "tags": [
+ "zone.zone"
+ ],
+ "x-openvibe-capabilities": [
+ "zone.zone.list"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "zone-control",
+ "security": [
+ {
+ "openvibeToken": [
+ "zone.zone.list"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/zone.object-zone-list-query.v1"
+ },
+ "parameters": [
+ {
+ "name": "project_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "env",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "enum": [
+ "sandbox",
+ "production"
+ ]
+ }
+ },
+ {
+ "name": "include_deleted",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "enum": [
+ true,
+ false,
+ "true",
+ "false"
+ ],
+ "description": "Also list zones in state deleted. Default false."
+ },
+ "description": "Also list zones in state deleted. Default false."
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "oneOf": [
+ {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 200
+ },
+ {
+ "type": "string",
+ "pattern": "^(?:[1-9][0-9]?|1[0-9]{2}|200)$"
+ }
+ ],
+ "description": "Items per page, 1 to 200. A query string carries it as a string, which must name the same range."
+ },
+ "description": "Items per page, 1 to 200. A query string carries it as a string, which must name the same range."
+ },
+ {
+ "name": "cursor",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "maxLength": 512,
+ "description": "next_cursor of the previous page."
+ },
+ "description": "next_cursor of the previous page."
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/zone.object-zone-list.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "operationId": "post_api_v1_projects_project_id_zones",
+ "summary": "Create a named object zone in one environment of a project.",
+ "description": "**zone.zone.create** (public): Create a named object zone in one environment of a project. POST /api/v1/projects/:project_id/zones with an Idempotency-Key header answers 201 with the zone. A name held by a provisioning or active zone of that environment answers 409 zone.name_taken, a name held by a deleting or deleted one answers 409 zone.name_retired, and going over the project's zones quota answers 403 zone.quota_exceeded. Zone provisions the zone in Media before it becomes active. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",
+ "tags": [
+ "zone.zone"
+ ],
+ "x-openvibe-capabilities": [
+ "zone.zone.create"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "zone-control",
+ "security": [
+ {
+ "openvibeToken": [
+ "zone.zone.create"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/zone.object-zone-create-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "project_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/zone.object-zone.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",
+ "summary": "Read OpenVibe.Zone's resource index (ADR-048): GET /api/v1/resources lists the zones Zone owns as a page of common.resource-summary@1 (common.resource-list-result@1, kind zone.object-zone, id zon_<ULI",
+ "description": "**zone.resource.read** (first-party): Read OpenVibe.Zone's resource index (ADR-048): GET /api/v1/resources lists the zones Zone owns as a page of common.resource-summary@1 (common.resource-list-result@1, kind zone.object-zone, id zon_<ULID>, with the project_id of the Network project that owns it), and GET /api/v1/resources/:ovrn reads one. Every authority serves these for OpenVibe.Services to fan out and merge.",
+ "tags": [
+ "zone.resource"
+ ],
+ "x-openvibe-capabilities": [
+ "zone.resource.read"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "zone.resource.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/common.resource-list-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/resources/{ovrn}": {
+ "get": {
+ "operationId": "get_api_v1_resources_ovrn",
+ "summary": "Read OpenVibe.Zone's resource index (ADR-048): GET /api/v1/resources lists the zones Zone owns as a page of common.resource-summary@1 (common.resource-list-result@1, kind zone.object-zone, id zon_<ULI",
+ "description": "**zone.resource.read** (first-party): Read OpenVibe.Zone's resource index (ADR-048): GET /api/v1/resources lists the zones Zone owns as a page of common.resource-summary@1 (common.resource-list-result@1, kind zone.object-zone, id zon_<ULID>, with the project_id of the Network project that owns it), and GET /api/v1/resources/:ovrn reads one. Every authority serves these for OpenVibe.Services to fan out and merge.",
+ "tags": [
+ "zone.resource"
+ ],
+ "x-openvibe-capabilities": [
+ "zone.resource.read"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "zone.resource.read"
+ ]
+ }
+ ],
+ "parameters": [
+ {
+ "name": "ovrn",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/common.resource-list-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/resources/control": {
+ "post": {
+ "operationId": "post_api_v1_resources_control",
+ "summary": "Control a zone through OpenVibe.Zone's control API (ADR-048, plan T13 step 10): POST /api/v1/resources/control takes one common.resource-control-request@1 naming a zone.object-zone by its OVRN, from a",
+ "description": "**zone.resource.control** (first-party): Control a zone through OpenVibe.Zone's control API (ADR-048, plan T13 step 10): POST /api/v1/resources/control takes one common.resource-control-request@1 naming a zone.object-zone by its OVRN, from a first-party service (OpenVibe.Services) acting for the person in on_behalf_of, who must be an admin or owner of the project. Action: delete (recursive: the zone answers NoSuchBucket at once, Media tombstones and purges every object with no restore window, and the name is retired). The default zone, and a zone holding an object under a retention hold, are refused. Any other action is refused unsupported. dry_run decides and explains without applying. The first answer per (caller, project, idempotency_key) is kept and returned for a repeat; the same key with another request is refused. Answers common.resource-control-result@1.",
+ "tags": [
+ "zone.resource"
+ ],
+ "x-openvibe-capabilities": [
+ "zone.resource.control"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "standard",
+ "security": [
+ {
+ "openvibeToken": [
+ "zone.resource.control"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/common.resource-control-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/common.resource-control-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/zones/{zone_id}": {
+ "delete": {
+ "operationId": "delete_api_v1_zones_zone_id",
+ "summary": "Delete an object zone: DELETE /api/v1/zones/:zone_id answers 202 with the zone in state deleting.",
+ "description": "**zone.zone.delete** (public): Delete an object zone: DELETE /api/v1/zones/:zone_id answers 202 with the zone in state deleting. A zone with objects needs recursive=true (else 409 zone.not_empty), a zone with a held object answers 409 zone.held, and the default zone answers 409 zone.default. Media tombstones every object at once, with no restore window, and erases every copy within 24 hours. A hold placed after the request delays only that object's purge. The name is retired, never reused. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",
+ "tags": [
+ "zone.zone"
+ ],
+ "x-openvibe-capabilities": [
+ "zone.zone.delete"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "zone-control",
+ "security": [
+ {
+ "openvibeToken": [
+ "zone.zone.delete"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/zone.object-zone-delete-query.v1"
+ },
+ "parameters": [
+ {
+ "name": "zone_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "recursive",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "enum": [
+ true,
+ false,
+ "true",
+ "false"
+ ],
+ "description": "Delete the zone's objects too. Default false."
+ },
+ "description": "Delete the zone's objects too. Default false."
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/zone.object-zone.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ },
+ "get": {
+ "operationId": "get_api_v1_zones_zone_id",
+ "summary": "Read one object zone: GET /api/v1/zones/:zone_id.",
+ "description": "**zone.zone.read** (public): Read one object zone: GET /api/v1/zones/:zone_id. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",
+ "tags": [
+ "zone.zone"
+ ],
+ "x-openvibe-capabilities": [
+ "zone.zone.read"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "zone-control",
+ "security": [
+ {
+ "openvibeToken": [
+ "zone.zone.read"
+ ]
+ }
+ ],
+ "parameters": [
+ {
+ "name": "zone_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/zone.object-zone.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_zones_zone_id",
+ "summary": "Change an object zone's quota_bytes or description: PATCH /api/v1/zones/:zone_id.",
+ "description": "**zone.zone.manage** (public): Change an object zone's quota_bytes or description: PATCH /api/v1/zones/:zone_id. Name, environment and access are immutable. A quota change takes effect once Media acknowledges it. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",
+ "tags": [
+ "zone.zone"
+ ],
+ "x-openvibe-capabilities": [
+ "zone.zone.manage"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "zone-control",
+ "security": [
+ {
+ "openvibeToken": [
+ "zone.zone.manage"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/zone.object-zone-update-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "zone_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/zone.object-zone.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/zones/{zone_id}/usage": {
+ "get": {
+ "operationId": "get_api_v1_zones_zone_id_usage",
+ "summary": "Read an object zone's usage: GET /api/v1/zones/:zone_id/usage.",
+ "description": "**zone.usage.read** (public): Read an object zone's usage: GET /api/v1/zones/:zone_id/usage. It answers Media's live-object snapshot and the month's hourly rollups. Zone never counts. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",
+ "tags": [
+ "zone.usage"
+ ],
+ "x-openvibe-capabilities": [
+ "zone.usage.read"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "zone-control",
+ "security": [
+ {
+ "openvibeToken": [
+ "zone.usage.read"
+ ]
+ }
+ ],
+ "parameters": [
+ {
+ "name": "zone_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/zone.object-zone-usage.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "common.resource-control-request.v1": {
+ "title": "ResourceControlRequest",
+ "description": "common.resource-control-request@1: one control operation OpenVibe.Services (or Actor's Console) sends to the authority that owns a resource (ADR-048, plan T13). Services never mutates another service's database; every create, update, start, stop, delete, resize, rotate, pair, grant or revoke is this request to the owning authority's control API, which decides and answers with a common.resource-control-result@1. A request names exactly one of resource (every action but create) or resource_kind (create, before the name exists). project_id is the tenancy boundary and equals the project segment of resource. idempotency_key makes a retry safe: the authority answers a repeated key with the first result. A sensitive action (money, publishing, deleting, physical control) is refused with confirmation_required until its owner approves; the retry carries that confirmation_id (roadmap WS-Z2). The cross-field rules are checked by contracts.resources.checkControlRequest.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "action",
+ "project_id",
+ "idempotency_key"
+ ],
+ "oneOf": [
+ {
+ "required": [
+ "resource"
+ ]
+ },
+ {
+ "required": [
+ "resource_kind"
+ ]
+ }
+ ],
+ "properties": {
+ "action": {
+ "enum": [
+ "create",
+ "update",
+ "delete",
+ "start",
+ "stop",
+ "suspend",
+ "resume",
+ "resize",
+ "rotate",
+ "pair",
+ "grant",
+ "revoke",
+ "archive"
+ ]
+ },
+ "resource": {
+ "$ref": "#/components/schemas/common.resource-name.v1",
+ "description": "The resource acted on; required for every action but create."
+ },
+ "resource_kind": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{0,31}\\.[a-z][a-z0-9-]{0,39}$",
+ "description": "service.type (a common.resource-summary@1 kind), for create, before the resource name exists."
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "params": {
+ "type": "object",
+ "description": "Action-specific input, shaped by the authority's own contract (never redefined here)."
+ },
+ "idempotency_key": {
+ "type": "string",
+ "minLength": 8,
+ "maxLength": 200
+ },
+ "on_behalf_of": {
+ "$ref": "#/components/schemas/identity.subject-ref.v1",
+ "description": "The subject whose grant the authority checks; the caller's own token says who sent it."
+ },
+ "confirmation_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 80,
+ "description": "The approved confirmation from an earlier confirmation_required answer."
+ },
+ "dry_run": {
+ "type": "boolean",
+ "default": false,
+ "description": "Decide and explain without applying."
+ },
+ "trace_id": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{32}$"
+ }
+ }
+ },
+ "common.resource-control-result.v1": {
+ "title": "ResourceControlResult",
+ "description": "common.resource-control-result@1: the authority's answer to a common.resource-control-request@1 (ADR-048, plan T13). state is the outcome, not the resource's own state: done (applied), pending (accepted and asynchronous; the authority reports completion later on its events), refused (a policy or confirmation gate said no) or failed (an error). State rules: refused carries problem or confirmation_required; failed carries problem; done and pending carry neither; confirmation_required appears only on refused. result carries the authority's own response document and never another service's rows. The state rules are checked by contracts.resources.checkControlResult.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "action",
+ "state",
+ "at"
+ ],
+ "properties": {
+ "action": {
+ "type": "string"
+ },
+ "resource": {
+ "$ref": "#/components/schemas/common.resource-name.v1"
+ },
+ "state": {
+ "enum": [
+ "done",
+ "pending",
+ "refused",
+ "failed"
+ ]
+ },
+ "result": {
+ "type": "object"
+ },
+ "confirmation_required": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "confirmation_id",
+ "reason"
+ ],
+ "properties": {
+ "confirmation_id": {
+ "type": "string",
+ "maxLength": 80
+ },
+ "reason": {
+ "type": "string"
+ }
+ },
+ "description": "The action needs its owner's approval; retry the same request with this confirmation_id once approved."
+ },
+ "problem": {
+ "$ref": "#/components/schemas/errors.problem.v1",
+ "description": "RFC 9457 body when refused or failed."
+ },
+ "at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "common.resource-list-result.v1": {
+ "title": "ResourceListResult",
+ "description": "A page of the resource index (roadmap WS-Z7).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "resources",
+ "next_cursor"
+ ],
+ "properties": {
+ "resources": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/common.resource-summary.v1"
+ }
+ },
+ "next_cursor": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "partial": {
+ "type": "array",
+ "description": "Authorities a merged index (OpenVibe.Services, ADR-048) could not read for this page: a slow or failing authority is omitted, never fails the page. Each entry names the authority and the problem code it answered. Added in 1.1.0; a single authority's own index leaves it out.",
+ "items": {
+ "type": "object",
+ "required": [
+ "service",
+ "code"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "service": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,39}$"
+ },
+ "code": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
+ }
+ }
+ }
+ }
+ }
+ },
+ "common.resource-name.v1": {
+ "title": "ResourceName",
+ "description": "common.resource-name@1: the one stable, cross-service name of an OpenVibe resource (OVRN; ADR-034 section 2, ADR-048, plan T13): ovrn:<service>:<project_id>:<type>/<id>, for example ovrn:media:prj_01J0000000000000000000000A:object/med_01K0000000000000000000000B. The service segment is the authority that owns the resource; the project segment is its tenancy boundary; type is the second half of a common.resource-summary@1 kind and id is that summary's id, so the name composes from any resource summary. It is used in grant scopes, audit rows, event subjects, usage records, bill lines and console URLs. It never names a person, an app, a request, a session or an event. The pattern is the one common.usage-recorded@1 resource and common.resource-summary@1 ovrn already use.",
+ "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}$",
+ "not": {
+ "pattern": "/(usr|gst|app|mod|svc|job|evt|req)_"
+ }
+ },
+ "common.resource-summary.v1": {
+ "title": "ResourceSummary",
+ "description": "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.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "kind",
+ "service",
+ "state",
+ "created_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "kind": {
+ "type": "string",
+ "description": "service.kind, e.g. watch.watch, actor.actor, media.object, events.subscription"
+ },
+ "service": {
+ "type": "string"
+ },
+ "project_id": {
+ "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": "#/components/schemas/identity.subject-ref.v1"
+ },
+ "name": {
+ "type": "string"
+ },
+ "state": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "usage": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "number",
+ "minimum": 0
+ },
+ "properties": {},
+ "description": "cost units by name (WS-Z1)"
+ },
+ "links": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "self": {
+ "type": "string",
+ "format": "uri"
+ },
+ "console": {
+ "type": "string",
+ "format": "uri"
+ },
+ "docs": {
+ "type": "string",
+ "format": "uri"
+ }
+ }
+ }
+ }
+ },
+ "errors.problem.v1": {
+ "title": "Problem",
+ "description": "RFC 9457 problem details (application/problem+json) with a stable OpenVibe error code and trace identifiers. The legacy 'error' string is allowed during migration so existing clients keep working.",
+ "type": "object",
+ "required": [
+ "type",
+ "title",
+ "status",
+ "code"
+ ],
+ "additionalProperties": true,
+ "properties": {
+ "type": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "title": {
+ "type": "string",
+ "minLength": 1
+ },
+ "status": {
+ "type": "integer",
+ "minimum": 400,
+ "maximum": 599
+ },
+ "detail": {
+ "type": "string"
+ },
+ "instance": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "code": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*$"
+ },
+ "request_id": {
+ "type": "string"
+ },
+ "trace_id": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{32}$"
+ },
+ "errors": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "message"
+ ],
+ "properties": {
+ "path": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "error": {
+ "type": "string",
+ "description": "Deprecated compatibility field: same text as title/detail for clients that read {error}."
+ }
+ }
+ },
+ "identity.subject-ref.v1": {
+ "title": "SubjectRef",
+ "description": "The one cross-platform reference to an actor. Issued by OpenVibe.Network. Never a service-local integer id.",
+ "oneOf": [
+ {
+ "type": "object",
+ "required": [
+ "type",
+ "id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "type": {
+ "const": "user"
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ },
+ "description": "UserSubject"
+ },
+ {
+ "type": "object",
+ "required": [
+ "type",
+ "id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "type": {
+ "const": "guest"
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^gst_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ },
+ "description": "GuestSubject"
+ },
+ {
+ "type": "object",
+ "required": [
+ "type",
+ "id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "type": {
+ "const": "app"
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^app_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ },
+ "description": "AppSubject"
+ },
+ {
+ "type": "object",
+ "required": [
+ "type",
+ "id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "type": {
+ "const": "mod"
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^mod_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ },
+ "description": "ModSubject"
+ },
+ {
+ "type": "object",
+ "required": [
+ "type",
+ "id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "type": {
+ "enum": [
+ "service",
+ "system"
+ ]
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,39}$"
+ }
+ },
+ "description": "PrincipalSubject"
+ },
+ {
+ "type": "object",
+ "required": [
+ "type",
+ "id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "type": {
+ "const": "agent"
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ },
+ "description": "AgentSubject: an agent (OpenVibe.Actor, or a developer app's agent) acting under grants its owner delegated (roadmap WS-Z2). In an event envelope, on_behalf_of names the owner."
+ }
+ ]
+ },
+ "zone.object-zone-create-request.v1": {
+ "title": "ObjectZoneCreateRequest",
+ "description": "zone.object-zone-create-request@1 (ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "env",
+ "name"
+ ],
+ "properties": {
+ "env": {
+ "enum": [
+ "sandbox",
+ "production"
+ ]
+ },
+ "name": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 63,
+ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
+ "not": {
+ "const": "default"
+ }
+ },
+ "description": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 200
+ },
+ "quota_bytes": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "minimum": 1
+ }
+ }
+ },
+ "zone.object-zone-delete-query.v1": {
+ "title": "ObjectZoneDeleteQuery",
+ "description": "zone.object-zone-delete-query@1 (ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "recursive": {
+ "enum": [
+ true,
+ false,
+ "true",
+ "false"
+ ],
+ "description": "Delete the zone's objects too. Default false."
+ }
+ }
+ },
+ "zone.object-zone-list-query.v1": {
+ "title": "ObjectZoneListQuery",
+ "description": "zone.object-zone-list-query@1 (ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "env": {
+ "enum": [
+ "sandbox",
+ "production"
+ ]
+ },
+ "include_deleted": {
+ "enum": [
+ true,
+ false,
+ "true",
+ "false"
+ ],
+ "description": "Also list zones in state deleted. Default false."
+ },
+ "limit": {
+ "oneOf": [
+ {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 200
+ },
+ {
+ "type": "string",
+ "pattern": "^(?:[1-9][0-9]?|1[0-9]{2}|200)$"
+ }
+ ],
+ "description": "Items per page, 1 to 200. A query string carries it as a string, which must name the same range."
+ },
+ "cursor": {
+ "type": "string",
+ "maxLength": 512,
+ "description": "next_cursor of the previous page."
+ }
+ }
+ },
+ "zone.object-zone-list.v1": {
+ "title": "ObjectZoneList",
+ "description": "zone.object-zone-list@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "zones",
+ "next_cursor"
+ ],
+ "properties": {
+ "zones": {
+ "type": "array",
+ "maxItems": 200,
+ "items": {
+ "$ref": "#/components/schemas/zone.object-zone.v1"
+ }
+ },
+ "next_cursor": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 512,
+ "description": "Pass as cursor for the next page; null on the last page."
+ }
+ }
+ },
+ "zone.object-zone-update-request.v1": {
+ "title": "ObjectZoneUpdateRequest",
+ "description": "zone.object-zone-update-request@1 (ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.",
+ "type": "object",
+ "additionalProperties": false,
+ "minProperties": 1,
+ "properties": {
+ "description": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 200
+ },
+ "quota_bytes": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "minimum": 1,
+ "description": "null removes the zone's own ceiling, so only the environment quota applies."
+ }
+ }
+ },
+ "zone.object-zone-usage.v1": {
+ "title": "ObjectZoneUsage",
+ "description": "zone.object-zone-usage@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "zone_id",
+ "ovrn",
+ "current",
+ "window_start",
+ "window_end",
+ "series"
+ ],
+ "properties": {
+ "zone_id": {
+ "type": "string",
+ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "ovrn": {
+ "type": "string",
+ "pattern": "^ovrn:zone:prj_[0-9A-HJKMNP-TV-Z]{26}:object-zone/zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "current": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "stored_bytes",
+ "objects",
+ "as_of"
+ ],
+ "properties": {
+ "stored_bytes": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "objects": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "as_of": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "window_start": {
+ "type": "string",
+ "format": "date-time",
+ "description": "The start of the current UTC month."
+ },
+ "window_end": {
+ "type": "string",
+ "format": "date-time",
+ "description": "The end of the last closed hour that Media has reported."
+ },
+ "series": {
+ "type": "array",
+ "maxItems": 32,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "capability",
+ "unit",
+ "quantity"
+ ],
+ "properties": {
+ "capability": {
+ "type": "string",
+ "pattern": "^media\\.object\\.[a-z0-9_]+$"
+ },
+ "unit": {
+ "enum": [
+ "byte_hours",
+ "requests",
+ "egress_bytes"
+ ]
+ },
+ "quantity": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ }
+ }
+ },
+ "zone.object-zone.v1": {
+ "title": "ObjectZone",
+ "description": "zone.object-zone@1 (ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "ovrn",
+ "project_id",
+ "env",
+ "name",
+ "default",
+ "access",
+ "state",
+ "media_namespace",
+ "quota_bytes",
+ "revision",
+ "created_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "ovrn": {
+ "type": "string",
+ "pattern": "^ovrn:zone:prj_[0-9A-HJKMNP-TV-Z]{26}:object-zone/zon_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The resource name used in grants, audit, events, usage records and bills (ADR-034 section 2)."
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "env": {
+ "enum": [
+ "sandbox",
+ "production"
+ ],
+ "description": "Fixed at creation. A sandbox key never resolves a production zone."
+ },
+ "name": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 63,
+ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
+ "description": "The S3 bucket name. It is unique among all the zones of one project environment, deleted ones included: a deleted zone's name is retired, never given to another zone. It is valid as an S3 bucket name."
+ },
+ "default": {
+ "type": "boolean",
+ "description": "The environment's default zone, named `default`. Its media_namespace is the environment root."
+ },
+ "description": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 200
+ },
+ "access": {
+ "enum": [
+ "private",
+ "per-object"
+ ],
+ "description": "private: every named zone in v1. Its objects never have a public_url and never enter a public cache. per-object: only the default zone, whose existing objects keep their own visibility (public, unlisted or private), public URLs and cached copies, as media.object@1 allows. The S3 surface never serves an object without a signature, in either kind of zone. Publication of a named zone would add a value in a later minor version."
+ },
+ "state": {
+ "enum": [
+ "provisioning",
+ "active",
+ "deleting",
+ "deleted"
+ ],
+ "description": "provisioning: Media has not yet acknowledged the zone. active: it serves requests. deleting: requests answer NoSuchBucket while Media tombstones the objects and erases every copy. deleted: no object or copy remains. The record is kept, and its name stays retired."
+ },
+ "media_namespace": {
+ "type": "string",
+ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}(\\.sandbox)?(\\.zon_[0-9A-HJKMNP-TV-Z]{26})?$",
+ "description": "Where Media keeps the zone's objects. It is the environment root for the default zone, and <root>.<id> for every other zone."
+ },
+ "quota_bytes": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "minimum": 1,
+ "description": "An optional byte ceiling for this zone. Media enforces it together with the project environment's byte quota, and a write over either is refused with QuotaExceeded. null means only the environment quota applies."
+ },
+ "usage": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "stored_bytes",
+ "objects",
+ "as_of"
+ ],
+ "description": "A snapshot of the live objects, as Media reports it. Zone never counts. Billed usage comes from Media's hourly media.usage.recorded rollups, whose dimension is this id in lowercase.",
+ "properties": {
+ "stored_bytes": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "objects": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "as_of": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "deletion": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "requested_at",
+ "objects_remaining",
+ "held_objects",
+ "completed_at"
+ ],
+ "description": "Progress of a zone deletion, as Media reports it.",
+ "properties": {
+ "requested_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "objects_remaining": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Objects not yet tombstoned, plus tombstoned objects with a copy not yet confirmed erased."
+ },
+ "held_objects": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Objects whose erasure waits for a retention hold (ADR-006). They are already unreadable."
+ },
+ "completed_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ }
+ },
+ "revision": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Raised by every change. Zone sends it when it provisions the change in Media, and Media applies only a higher revision."
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "updated_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "properties": {
+ "default": {
+ "const": true
+ }
+ },
+ "required": [
+ "default"
+ ]
+ },
+ "then": {
+ "properties": {
+ "name": {
+ "const": "default"
+ },
+ "access": {
+ "const": "per-object"
+ },
+ "media_namespace": {
+ "type": "string",
+ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}(\\.sandbox)?$"
+ }
+ }
+ },
+ "else": {
+ "properties": {
+ "name": {
+ "not": {
+ "const": "default"
+ }
+ },
+ "access": {
+ "const": "private"
+ },
+ "media_namespace": {
+ "type": "string",
+ "pattern": "\\.zon_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "env": {
+ "const": "sandbox"
+ }
+ },
+ "required": [
+ "env"
+ ]
+ },
+ "then": {
+ "properties": {
+ "media_namespace": {
+ "type": "string",
+ "pattern": "^app\\.prj_[0-9A-HJKMNP-TV-Z]{26}\\.sandbox(\\.|$)"
+ }
+ }
+ },
+ "else": {
+ "properties": {
+ "media_namespace": {
+ "type": "string",
+ "not": {
+ "pattern": "\\.sandbox(\\.|$)"
+ }
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "state": {
+ "enum": [
+ "deleting",
+ "deleted"
+ ]
+ }
+ },
+ "required": [
+ "state"
+ ]
+ },
+ "then": {
+ "required": [
+ "deletion"
+ ]
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "state": {
+ "const": "deleted"
+ }
+ },
+ "required": [
+ "state"
+ ]
+ },
+ "then": {
+ "properties": {
+ "deletion": {
+ "type": "object",
+ "properties": {
+ "objects_remaining": {
+ "const": 0
+ },
+ "held_objects": {
+ "const": 0
+ },
+ "completed_at": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "securitySchemes": {
+ "openvibeToken": {
+ "type": "http",
+ "scheme": "bearer",
+ "bearerFormat": "JWT",
+ "description": "An OpenVibe Network token for this service: a person's token, an app token (client credentials, audience of the service) or a first-party service token. Its `cap` claim must include the capability the operation names."
+ }
+ }
+ }
+}
@@ -175561,7 +175561,7 @@ export type NetworkConfirmationChangedPayload = {
@@ -175561,7 +175561,7 @@ export type NetworkConfirmationChangedPayload = {
/** [email protected] (owner: zone) */
/** [email protected] (owner: zone) */
/**
/**
- * zone.object-zone@1 (PLANNED; ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.
+ * zone.object-zone@1 (ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.
*/
*/
export type ObjectZone = {
export type ObjectZone = {
[k: string]: unknown | undefined;
[k: string]: unknown | undefined;
@@ -175634,7 +175634,7 @@ export type ObjectZone = {
@@ -175634,7 +175634,7 @@ export type ObjectZone = {
/** [email protected] (owner: zone) */
/** [email protected] (owner: zone) */
/**
/**
- * zone.object-zone-create-request@1 (PLANNED; ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.
+ * zone.object-zone-create-request@1 (ADR-031 amendment 2026-10-02): the body of POST /api/v1/projects/{project_id}/zones on OpenVibe.Zone, sent with an Idempotency-Key header. The answer is 201 with a zone.object-zone@1 in state provisioning or active. A name held by a zone of that environment in state provisioning or active answers 409 zone.name_taken, and one held by a zone in state deleting or deleted answers 409 zone.name_retired, because a name belongs to one zone for ever; and going over the project's zones quota answers 403 zone.quota_exceeded. `default` cannot be requested, because Zone creates that zone with the environment.
*/
*/
export interface ObjectZoneCreateRequest {
export interface ObjectZoneCreateRequest {
env: "sandbox" | "production";
env: "sandbox" | "production";
@@ -175645,7 +175645,7 @@ export interface ObjectZoneCreateRequest {
@@ -175645,7 +175645,7 @@ export interface ObjectZoneCreateRequest {
/** [email protected] (owner: zone) */
/** [email protected] (owner: zone) */
/**
/**
- * zone.object-zone-update-request@1 (PLANNED; ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.
+ * zone.object-zone-update-request@1 (ADR-031 amendment 2026-10-02): the body of PATCH /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.manage). Only quota_bytes and description change. Name, environment and access are immutable, so a body naming them is refused. The answer is 200 with the zone.object-zone@1. A quota change takes effect once Media acknowledges it, and lowering quota_bytes below the zone's stored bytes only refuses later writes. It never deletes an object.
*/
*/
export interface ObjectZoneUpdateRequest {
export interface ObjectZoneUpdateRequest {
description?: string | null;
description?: string | null;
@@ -175657,7 +175657,7 @@ export interface ObjectZoneUpdateRequest {
@@ -175657,7 +175657,7 @@ export interface ObjectZoneUpdateRequest {
/** [email protected] (owner: zone) */
/** [email protected] (owner: zone) */
/**
/**
- * zone.object-zone-list-query@1 (PLANNED; ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.
+ * zone.object-zone-list-query@1 (ADR-031 amendment 2026-10-02): the query string of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list). Without env it lists both environments the caller's grant reaches. A sandbox-only grant never lists production zones.
*/
*/
export interface ObjectZoneListQuery {
export interface ObjectZoneListQuery {
env?: "sandbox" | "production";
env?: "sandbox" | "production";
@@ -175677,7 +175677,7 @@ export interface ObjectZoneListQuery {
@@ -175677,7 +175677,7 @@ export interface ObjectZoneListQuery {
/** [email protected] (owner: zone) */
/** [email protected] (owner: zone) */
/**
/**
- * zone.object-zone-list@1 (PLANNED; ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.
+ * zone.object-zone-list@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/projects/{project_id}/zones on OpenVibe.Zone (capability zone.zone.list), filtered by zone.object-zone-list-query@1. Zones come oldest first, the environment's default zone before the others. Deleted zones are listed only with include_deleted=true. contracts.zones.checkObjectZoneList also checks each zone's identity. More fields may be added.
*/
*/
export interface ObjectZoneList {
export interface ObjectZoneList {
/**
/**
@@ -175692,7 +175692,7 @@ export interface ObjectZoneList {
@@ -175692,7 +175692,7 @@ export interface ObjectZoneList {
/** [email protected] (owner: zone) */
/** [email protected] (owner: zone) */
/**
/**
- * zone.object-zone-delete-query@1 (PLANNED; ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.
+ * zone.object-zone-delete-query@1 (ADR-031 amendment 2026-10-02): the query string of DELETE /api/v1/zones/{zone_id} on OpenVibe.Zone (capability zone.zone.delete). The answer is 202 with the zone.object-zone@1 in state deleting. Without recursive=true, a zone that still holds live objects answers 409 zone.not_empty. A zone with an object under a retention hold answers 409 zone.held, as media.object.delete refuses a held object. The default zone always answers 409 zone.default. Deletion goes through Media's one deletion path: every object is tombstoned at once, with no restore window, and every copy is erased within 24 hours. A hold placed after the request delays only that object's purge. The zone's name is retired: no later zone of the environment can take it.
*/
*/
export interface ObjectZoneDeleteQuery {
export interface ObjectZoneDeleteQuery {
/**
/**
@@ -175703,7 +175703,7 @@ export interface ObjectZoneDeleteQuery {
@@ -175703,7 +175703,7 @@ export interface ObjectZoneDeleteQuery {
/** [email protected] (owner: zone) */
/** [email protected] (owner: zone) */
/**
/**
- * zone.object-zone-usage@1 (PLANNED; ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.
+ * zone.object-zone-usage@1 (ADR-031 amendment 2026-10-02): the answer of GET /api/v1/zones/{zone_id}/usage on OpenVibe.Zone (capability zone.usage.read). Zone never counts. current is the live-object snapshot that Media's internal zone endpoint returns. series sums Media's hourly media.usage.recorded rollups (common.usage-recorded@1) whose dimension is this zone id in lowercase, over the closed hours of the current UTC month. Rollups are counts; Billing rates only platform.usage-sample readings (billing.usage.record). Zone's control operations are not metered. ovrn is the OVRN of zone_id, never of another zone (contracts.zones.checkObjectZoneUsage). More fields may be added.
*/
*/
export interface ObjectZoneUsage {
export interface ObjectZoneUsage {
zone_id: string;
zone_id: string;
@@ -184072,3 +184072,333 @@ export interface PlayPlayerAchievements {
@@ -184072,3 +184072,333 @@ export interface PlayPlayerAchievements {
unlocked_at: string;
unlocked_at: string;
}[];
}[];
}
}
+
+/** [email protected] (owner: media) */
+/**
+ * media.access-key@1 (ADR-031, amended 2026-10-14): one S3 access key of OpenVibe.Media's S3 surface (https://s3.openvibe.media). It is bound to one project environment, reads and/or writes, and may be scoped to some zones of that environment (zone.object-zone@1 ids; empty means every zone). Media issues and verifies it: SigV4 needs the secret, so the party that checks signatures keeps it, encrypted, and shows it once (media.access-key-issued@1). The secret is never part of this record. A revoked key is refused at once; a key scoped only to a zone that is deleted is revoked with it. More fields may be added.
+ */
+export interface AccessKey {
+ /**
+ * The S3 access key id.
+ */
+ id: string;
+ project_id: string;
+ env: "sandbox" | "production";
+ /**
+ * What uses the key, as the person who issued it named it.
+ */
+ name: string;
+ permissions: "read" | "write" | "read-write";
+ /**
+ * The zones it may reach; empty = every zone of the environment.
+ *
+ * @maxItems 20
+ */
+ zones:
+ | []
+ | [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]
+ | [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, 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,
+ 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,
+ 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,
+ 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
+ ];
+ created_by: string;
+ created_at: string;
+ /**
+ * The last signed request, to the minute.
+ */
+ last_used_at: string | null;
+ revoked_at: string | null;
+}
+
+/** [email protected] (owner: media) */
+/**
+ * media.access-key-issue-request@1 (ADR-031): the body of POST /api/v1/projects/:project_id/access-keys on OpenVibe.Media (capability media.access_key.manage), sent by a first-party service (OpenVibe.Zone, OpenVibe.Services) for the person in on_behalf_of, who must be an admin or owner of the project (Network decides). The answer is 201 media.access-key-issued@1. A zone that is not an active zone of this project environment answers 404 media.access_key.unknown_zone; more than 50 live keys in one environment answer 409 media.access_key.limit.
+ */
+export interface AccessKeyIssueRequest {
+ env: "sandbox" | "production";
+ name: string;
+ permissions?: "read" | "write" | "read-write";
+ /**
+ * @maxItems 20
+ */
+ zones?:
+ | []
+ | [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]
+ | [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, 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,
+ 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,
+ 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,
+ 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
+ ];
+ on_behalf_of: string;
+}
+
+/** [email protected] (owner: media) */
+/**
+ * media.access-key-issued@1 (ADR-031): the 201 of POST /api/v1/projects/:project_id/access-keys. `secret` is the S3 secret access key; it is in this answer only, and Media never shows it again. A caller relays it to the person once and keeps no copy.
+ */
+export interface AccessKeyIssued {
+ key: AccessKey;
+ secret: string;
+}
+
+/** [email protected] (owner: media) */
+/**
+ * media.access-key-list@1 (ADR-031): GET /api/v1/projects/:project_id/access-keys (capability media.access_key.manage, for a project admin in on_behalf_of): every key of the project, newest first, revoked ones included. No secret is ever listed.
+ */
+export interface AccessKeyList {
+ /**
+ * @maxItems 500
+ */
+ keys: AccessKey[];
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "id": "media.access_key.manage",
+ "version": "1.0",
+ "owner": "media",
+ "status": "active",
+ "visibility": "first-party",
+ "sensitive": true,
+ "description": "Issue, list and revoke the S3 access keys of a project (ADR-031, amended 2026-10-14: Media issues the keys it verifies). A first-party service (OpenVibe.Zone, OpenVibe.Services) calls for the person in on_behalf_of, who must be an admin or owner of the project (Media asks Network, network.project.member.read). POST /api/v1/projects/:project_id/access-keys takes media.access-key-issue-request@1 and answers 201 media.access-key-issued@1, the only answer that carries the secret; GET lists media.access-key-list@1; DELETE /:key_id revokes at once. Loopback only.",
+ "permissions": [
+ "keys:manage"
+ ],
+ "resourceConstraints": [
+ "project"
+ ],
+ "quotaClass": "internal-write",
+ "events": [],
+ "implementedBy": [
+ "POST /api/v1/projects/:project_id/access-keys",
+ "GET /api/v1/projects/:project_id/access-keys",
+ "DELETE /api/v1/projects/:project_id/access-keys/:key_id"
+ ],
+ "inputSchema": "media.access-key-issue-request@1",
+ "outputSchema": "media.access-key-issued@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
+{
+ "id": "media.object.s3",
+ "version": "1.0",
+ "owner": "media",
+ "status": "active",
+ "visibility": "public",
+ "description": "OpenVibe.Media's S3 surface (ADR-031), path style at https://s3.openvibe.media/<zone name>/<key>: PutObject (one request, aws-chunked bodies included, Content-MD5 and x-amz-checksum-* checked), GetObject (ranges), HeadObject, DeleteObject, DeleteObjects, ListObjectsV2 and ListObjects (prefix, delimiter, pages), ListBuckets, HeadBucket, GetBucketLocation, and presigned GET and PUT (SigV4 query, 7 days at most). Everything else answers NotImplemented (multipart, copy, ACLs, policies, versioning; buckets are made and deleted only through OpenVibe.Zone). It is reached with a media.access-key@1 rather than a Network token: the key fixes the project and environment, so a bucket name names one zone of that environment, and a key scoped to zones reaches only those. A request is authorised as the project's app would be: the same quotas (the zone's and the environment's), the same objects as the native API, and Media's one deletion path (a held object is neither deleted nor overwritten). Responses carry a sandbox Content-Security-Policy.",
+ "permissions": [
+ "objects:read",
+ "objects:write"
+ ],
+ "resourceConstraints": [
+ "project"
+ ],
+ "quotaClass": "standard",
+ "events": [
+ "media.object.uploaded",
+ "media.object.deleted"
+ ],
+ "implementedBy": [
+ "GET s3:/",
+ "HEAD s3:/:zone",
+ "GET s3:/:zone",
+ "POST s3:/:zone?delete",
+ "PUT s3:/:zone/:key",
+ "GET s3:/:zone/:key",
+ "HEAD s3:/:zone/:key",
+ "DELETE s3:/:zone/:key"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "common.no-body@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+{
+ "id": "media.zone.provision",
+ "version": "1.0",
+ "owner": "media",
+ "status": "active",
+ "visibility": "first-party",
+ "sensitive": true,
+ "description": "Provision a zone in OpenVibe.Media (ADR-031 amendment 2026-10-02), granted only to OpenVibe.Zone: PUT /internal/zones/:zone_id with the zone.object-zone@1 record Zone owns. Media applies a record only when its revision is higher than the one it holds, checks it with contracts.zones.checkObjectZone, and refuses a name another zone of the environment holds (409 media.zone.name_retired) and any change of project, environment, name or namespace (409 media.zone.immutable). Media keeps a projection of each zone to resolve S3 bucket names, enforce the zone's quota_bytes beside the environment's, and answer NoSuchBucket for a zone that is deleting or deleted. GET /internal/zones/:zone_id reports the live objects and, while the zone is deleting, the objects remaining and held; Media tombstones and purges a deleting zone's objects (no restore window) and records when none remain. Loopback only.",
+ "permissions": [
+ "zones:provision"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "internal-write",
+ "events": [],
+ "implementedBy": [
+ "PUT /internal/zones/:zone_id",
+ "GET /internal/zones/:zone_id"
+ ],
+ "inputSchema": "zone.object-zone@1",
+ "outputSchema": "common.no-body@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "id": "zone.resource.control",
+ "version": "1.0",
+ "owner": "zone",
+ "status": "active",
+ "visibility": "first-party",
+ "sensitive": true,
+ "description": "Control a zone through OpenVibe.Zone's control API (ADR-048, plan T13 step 10): POST /api/v1/resources/control takes one common.resource-control-request@1 naming a zone.object-zone by its OVRN, from a first-party service (OpenVibe.Services) acting for the person in on_behalf_of, who must be an admin or owner of the project. Action: delete (recursive: the zone answers NoSuchBucket at once, Media tombstones and purges every object with no restore window, and the name is retired). The default zone, and a zone holding an object under a retention hold, are refused. Any other action is refused unsupported. dry_run decides and explains without applying. The first answer per (caller, project, idempotency_key) is kept and returned for a repeat; the same key with another request is refused. Answers common.resource-control-result@1.",
+ "permissions": [
+ "resources:control"
+ ],
+ "resourceConstraints": [
+ "project"
+ ],
+ "quotaClass": "standard",
+ "events": [],
+ "implementedBy": [
+ "POST /api/v1/resources/control"
+ ],
+ "inputSchema": "common.resource-control-request@1",
+ "outputSchema": "common.resource-control-result@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "id": "zone.resource.read",
+ "version": "1.0",
+ "owner": "zone",
+ "status": "active",
+ "visibility": "first-party",
+ "description": "Read OpenVibe.Zone's resource index (ADR-048): GET /api/v1/resources lists the zones Zone owns as a page of common.resource-summary@1 (common.resource-list-result@1, kind zone.object-zone, id zon_<ULID>, with the project_id of the Network project that owns it), and GET /api/v1/resources/:ovrn reads one. Every authority serves these for OpenVibe.Services to fan out and merge.",
+ "permissions": [
+ "resources:read"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "standard",
+ "events": [],
+ "implementedBy": [
+ "GET /api/v1/resources",
+ "GET /api/v1/resources/:ovrn"
+ ],
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "common.resource-list-result@1"
+}
@@ -2,9 +2,9 @@
@@ -2,9 +2,9 @@
"id": "zone.usage.read",
"id": "zone.usage.read",
"version": "1.0",
"version": "1.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "status": "active",
"visibility": "public",
"visibility": "public",
- "description": "PLANNED (ADR-031 amendment 2026-10-02): Read an object zone's usage: GET /api/v1/zones/:zone_id/usage. It answers Media's live-object snapshot and the month's hourly rollups. Zone never counts. It becomes active, and enters generated/openapi/zone.json, when OpenVibe.Zone implements the route.",
+ "description": "Read an object zone's usage: GET /api/v1/zones/:zone_id/usage. It answers Media's live-object snapshot and the month's hourly rollups. Zone never counts. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",
"outputSchema": "zone.object-zone-usage@1",
"outputSchema": "zone.object-zone-usage@1",
"permissions": [
"permissions": [
"zone:read"
"zone:read"
@@ -14,5 +14,8 @@
@@ -14,5 +14,8 @@
],
],
"quotaClass": "zone-control",
"quotaClass": "zone-control",
"events": [],
"events": [],
- "implementedBy": []
+ "implementedBy": [
+ "GET /api/v1/zones/:zone_id/usage"
+ ],
+ "inputSchema": "common.no-body@1"
}
}
@@ -2,9 +2,9 @@
@@ -2,9 +2,9 @@
"id": "zone.zone.create",
"id": "zone.zone.create",
"version": "1.0",
"version": "1.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "status": "active",
"visibility": "public",
"visibility": "public",
- "description": "PLANNED (ADR-031 amendment 2026-10-02): Create a named object zone in one environment of a project. POST /api/v1/projects/:project_id/zones with an Idempotency-Key header answers 201 with the zone. A name held by a provisioning or active zone of that environment answers 409 zone.name_taken, a name held by a deleting or deleted one answers 409 zone.name_retired, and going over the project's zones quota answers 403 zone.quota_exceeded. Zone provisions the zone in Media before it becomes active. It becomes active, and enters generated/openapi/zone.json, when OpenVibe.Zone implements the route.",
+ "description": "Create a named object zone in one environment of a project. POST /api/v1/projects/:project_id/zones with an Idempotency-Key header answers 201 with the zone. A name held by a provisioning or active zone of that environment answers 409 zone.name_taken, a name held by a deleting or deleted one answers 409 zone.name_retired, and going over the project's zones quota answers 403 zone.quota_exceeded. Zone provisions the zone in Media before it becomes active. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",
"inputSchema": "zone.object-zone-create-request@1",
"inputSchema": "zone.object-zone-create-request@1",
"outputSchema": "zone.object-zone@1",
"outputSchema": "zone.object-zone@1",
"permissions": [
"permissions": [
@@ -15,5 +15,7 @@
@@ -15,5 +15,7 @@
],
],
"quotaClass": "zone-control",
"quotaClass": "zone-control",
"events": [],
"events": [],
- "implementedBy": []
+ "implementedBy": [
+ "POST /api/v1/projects/:project_id/zones"
+ ]
}
}
@@ -2,10 +2,10 @@
@@ -2,10 +2,10 @@
"id": "zone.zone.delete",
"id": "zone.zone.delete",
"version": "1.0",
"version": "1.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "status": "active",
"visibility": "public",
"visibility": "public",
"sensitive": true,
"sensitive": true,
- "description": "PLANNED (ADR-031 amendment 2026-10-02): Delete an object zone: DELETE /api/v1/zones/:zone_id answers 202 with the zone in state deleting. A zone with objects needs recursive=true (else 409 zone.not_empty), a zone with a held object answers 409 zone.held, and the default zone answers 409 zone.default. Media tombstones every object at once, with no restore window, and erases every copy within 24 hours. A hold placed after the request delays only that object's purge. The name is retired, never reused. It becomes active, and enters generated/openapi/zone.json, when OpenVibe.Zone implements the route.",
+ "description": "Delete an object zone: DELETE /api/v1/zones/:zone_id answers 202 with the zone in state deleting. A zone with objects needs recursive=true (else 409 zone.not_empty), a zone with a held object answers 409 zone.held, and the default zone answers 409 zone.default. Media tombstones every object at once, with no restore window, and erases every copy within 24 hours. A hold placed after the request delays only that object's purge. The name is retired, never reused. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",
"inputSchema": "zone.object-zone-delete-query@1",
"inputSchema": "zone.object-zone-delete-query@1",
"outputSchema": "zone.object-zone@1",
"outputSchema": "zone.object-zone@1",
"permissions": [
"permissions": [
@@ -16,5 +16,7 @@
@@ -16,5 +16,7 @@
],
],
"quotaClass": "zone-control",
"quotaClass": "zone-control",
"events": [],
"events": [],
- "implementedBy": []
+ "implementedBy": [
+ "DELETE /api/v1/zones/:zone_id"
+ ]
}
}
@@ -2,9 +2,9 @@
@@ -2,9 +2,9 @@
"id": "zone.zone.list",
"id": "zone.zone.list",
"version": "1.0",
"version": "1.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "status": "active",
"visibility": "public",
"visibility": "public",
- "description": "PLANNED (ADR-031 amendment 2026-10-02): List a project's object zones: GET /api/v1/projects/:project_id/zones, filtered by env and paged. It becomes active, and enters generated/openapi/zone.json, when OpenVibe.Zone implements the route.",
+ "description": "List a project's object zones: GET /api/v1/projects/:project_id/zones, filtered by env and paged. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",
"inputSchema": "zone.object-zone-list-query@1",
"inputSchema": "zone.object-zone-list-query@1",
"outputSchema": "zone.object-zone-list@1",
"outputSchema": "zone.object-zone-list@1",
"permissions": [
"permissions": [
@@ -15,5 +15,7 @@
@@ -15,5 +15,7 @@
],
],
"quotaClass": "zone-control",
"quotaClass": "zone-control",
"events": [],
"events": [],
- "implementedBy": []
+ "implementedBy": [
+ "GET /api/v1/projects/:project_id/zones"
+ ]
}
}
@@ -2,9 +2,9 @@
@@ -2,9 +2,9 @@
"id": "zone.zone.manage",
"id": "zone.zone.manage",
"version": "1.0",
"version": "1.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "status": "active",
"visibility": "public",
"visibility": "public",
- "description": "PLANNED (ADR-031 amendment 2026-10-02): Change an object zone's quota_bytes or description: PATCH /api/v1/zones/:zone_id. Name, environment and access are immutable. A quota change takes effect once Media acknowledges it. It becomes active, and enters generated/openapi/zone.json, when OpenVibe.Zone implements the route.",
+ "description": "Change an object zone's quota_bytes or description: PATCH /api/v1/zones/:zone_id. Name, environment and access are immutable. A quota change takes effect once Media acknowledges it. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",
"inputSchema": "zone.object-zone-update-request@1",
"inputSchema": "zone.object-zone-update-request@1",
"outputSchema": "zone.object-zone@1",
"outputSchema": "zone.object-zone@1",
"permissions": [
"permissions": [
@@ -15,5 +15,7 @@
@@ -15,5 +15,7 @@
],
],
"quotaClass": "zone-control",
"quotaClass": "zone-control",
"events": [],
"events": [],
- "implementedBy": []
+ "implementedBy": [
+ "PATCH /api/v1/zones/:zone_id"
+ ]
}
}
@@ -2,9 +2,9 @@
@@ -2,9 +2,9 @@
"id": "zone.zone.read",
"id": "zone.zone.read",
"version": "1.0",
"version": "1.0",
"owner": "zone",
"owner": "zone",
- "status": "planned",
+ "status": "active",
"visibility": "public",
"visibility": "public",
- "description": "PLANNED (ADR-031 amendment 2026-10-02): Read one object zone: GET /api/v1/zones/:zone_id. It becomes active, and enters generated/openapi/zone.json, when OpenVibe.Zone implements the route.",
+ "description": "Read one object zone: GET /api/v1/zones/:zone_id. Callers: a developer app of the project with this capability, or a person who administers the project, signed in on openvibe.zone.",
"outputSchema": "zone.object-zone@1",
"outputSchema": "zone.object-zone@1",
"permissions": [
"permissions": [
"zone:read"
"zone:read"
@@ -14,5 +14,8 @@
@@ -14,5 +14,8 @@
],
],
"quotaClass": "zone-control",
"quotaClass": "zone-control",
"events": [],
"events": [],
- "implementedBy": []
+ "implementedBy": [
+ "GET /api/v1/zones/:zone_id"
+ ],
+ "inputSchema": "common.no-body@1"
}
}
@@ -8,7 +8,8 @@
@@ -8,7 +8,8 @@
"status": "beta",
"status": "beta",
"repository": "OpenVibers/OpenVibe.Media",
"repository": "OpenVibers/OpenVibe.Media",
"domains": [
"domains": [
- "openvibe.media"
+ "openvibe.media",
+ "s3.openvibe.media"
],
],
"publicOrigin": "https://openvibe.media",
"publicOrigin": "https://openvibe.media",
"health": "/healthz",
"health": "/healthz",
@@ -27,6 +28,7 @@
@@ -27,6 +28,7 @@
"position": 4
"position": 4
},
},
"capabilities": [
"capabilities": [
+ "media.access_key.manage",
"media.avatar.ingest",
"media.avatar.ingest",
"media.derivative.create",
"media.derivative.create",
"media.derivative.read",
"media.derivative.read",
@@ -35,10 +37,12 @@
@@ -35,10 +37,12 @@
"media.object.delete",
"media.object.delete",
"media.object.list",
"media.object.list",
"media.object.read",
"media.object.read",
+ "media.object.s3",
"media.object.upload",
"media.object.upload",
- "media.resource.read",
"media.resource.control",
"media.resource.control",
- "media.upload.create"
+ "media.resource.read",
+ "media.upload.create",
+ "media.zone.provision"
],
],
"eventsProduced": [
"eventsProduced": [
"media.clip.failed",
"media.clip.failed",
@@ -1,21 +1,20 @@
@@ -1,21 +1,20 @@
{
{
- "version": "0.0.0",
+ "version": "0.1.0",
"contractRanges": {
"contractRanges": {
- "openvibe-contracts": ">=0.87.0 <1.0.0"
+ "openvibe-contracts": ">=0.173.0 <1.0.0"
},
},
"id": "zone",
"id": "zone",
"name": "OpenVibe.Zone",
"name": "OpenVibe.Zone",
- "status": "placeholder",
+ "status": "alpha",
"repository": "OpenVibers/OpenVibe.Zone",
"repository": "OpenVibers/OpenVibe.Zone",
"domains": [
"domains": [
"openvibe.zone",
"openvibe.zone",
"api.openvibe.zone"
"api.openvibe.zone"
],
],
- "publicOrigin": "https://api.openvibe.zone",
+ "publicOrigin": "https://openvibe.zone",
"exposure": {
"exposure": {
- "state": "placeholder",
- "publicSite": null,
- "note": "planned (ADR-031 amendment 2026-10-02); nothing is built or deployed, and no OpenVibe service serves openvibe.zone yet"
+ "state": "live",
+ "publicSite": "service"
},
},
"capabilities": [
"capabilities": [
"zone.zone.create",
"zone.zone.create",
@@ -23,30 +22,73 @@
@@ -23,30 +22,73 @@
"zone.zone.read",
"zone.zone.read",
"zone.zone.manage",
"zone.zone.manage",
"zone.zone.delete",
"zone.zone.delete",
- "zone.usage.read"
+ "zone.usage.read",
+ "zone.resource.read",
+ "zone.resource.control"
],
],
"eventsProduced": [],
"eventsProduced": [],
"eventsConsumed": [],
"eventsConsumed": [],
- "namespacesOwned": [],
+ "namespacesOwned": [
+ "zone.*"
+ ],
"lifecycle": {
"lifecycle": {
"liveness": {
"liveness": {
- "none": "not built yet; nothing listens on a loopback address"
+ "endpoint": "/api/health",
+ "means": "the process is up and answers HTTP; the database is what ready checks"
},
},
"shutdown": {
"shutdown": {
- "none": "not built yet; nothing runs or drains"
+ "signal": "SIGTERM",
+ "deadlineSeconds": 10,
+ "drains": [
+ "HTTP server closed",
+ "deletion poller stopped",
+ "Network key refresher stopped",
+ "store closed"
+ ]
},
},
"startupRecovery": {
"startupRecovery": {
- "none": "not built yet; nothing starts"
+ "resumes": [
+ {
+ "kind": "sessions",
+ "what": "sign-in sessions are cookies verified against Network's keys, fetched again at boot"
+ },
+ {
+ "kind": "state",
+ "what": "every zone is a committed row with a revision; a zone still provisioning, or deleting, is pushed to Media again (Media applies only a higher revision)"
+ },
+ {
+ "kind": "jobs",
+ "what": "the deletion poller resumes every zone in state deleting and marks it deleted once Media reports nothing left"
+ }
+ ]
},
},
"rollback": {
"rollback": {
- "none": "not built yet; nothing is released or rolled back"
+ "conditions": [
+ "automatic: ovhost deploy puts the previous sha back and restarts again when the ready URL does not answer 2xx within ready.timeoutSeconds after the restart (exit 3)",
+ "manual: ovhost rollback <service> --to <sha>"
+ ],
+ "window": "automatic only while ovhost waits for readiness after the restart; manual afterwards, as long as no blocker applies",
+ "blockers": {
+ "none": "migration 0001 only creates Zone's own tables; Media keeps serving every zone from its projection while Zone is down"
+ }
},
},
"contracts": {
"contracts": {
- "range": ">=0.87.0 <1.0.0"
+ "range": ">=0.173.0 <1.0.0"
},
},
"leases": {
"leases": {
- "none": "not built yet; nothing runs"
+ "none": "no claims: one process serves the site, the API and the deletion poller"
}
}
},
},
- "notes": "Named object zones per project environment (ADR-031 amendment 2026-10-02). Zone owns the zone record and its control API in its own PostgreSQL database (ADR-035). OpenVibe.Media owns the objects, bytes, replicas, S3 and native data APIs, quota enforcement, usage counting and the one deletion path. Planned: nothing is built."
+ "notes": "Port 5050, unit openvibe-zone.service, openvibe.zone (its own certificate; api.openvibe.zone waits for its DNS record). Named object zones per project environment (ADR-031 amendment 2026-10-02): Zone owns the zone record, its name (never reused), state, quota setting and control API in its own PostgreSQL database (ov_zone, ADR-035), and provisions every change in OpenVibe.Media, which owns the objects, bytes, replicas, the S3 and native data APIs, quota enforcement, usage counting, the access keys and the one deletion path. Zone never lists keys or holds bytes. Its site is the storage console: a project's zones, their usage, S3 access keys (issued by Media), and how to use them.",
+ "health": "/api/health",
+ "ready": "/api/ready",
+ "internalOrigin": "http://127.0.0.1:5050",
+ "site": {
+ "name": "Zone",
+ "icon": "zone",
+ "tagline": "Private S3-compatible storage for your projects.",
+ "what": "Named storage zones for each project and environment: buckets that the AWS SDKs, rclone and backup tools reach over S3, with access keys you scope to a zone and quotas you set.",
+ "legalProfile": "ugc",
+ "position": 36
+ }
}
}
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.172.2",
+ "version": "0.173.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",