openvibers/openvibe.contracts Commits
v0.33.0: ADR-027 Tools platform API; CHANGELOG, README and version 0.33.0
Changes
@@@ -4,6 +4,95 @@ 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.33.0 — 2026-09-23++++Additive: `compat.js` reports no breaking change against v0.32.0.++++**Tools platform API** (new ADR-027). The tools on openvibe.tools become one registry and one run API,++open to the SDK, with tiered, weighted quotas.++++- `tools.tool@1`: the **tool descriptor**, one per catalogue tool.++ - Identity: `id`, `family`, `name`, `summary`.++ - `status`: `stable` | `beta` | `preview` | `unavailable`, with `statusReason`.++ - How it runs:++ - `execution`: `client` | `sync` | `job`.++ - `api`: whether the run API exposes the tool.++ - `run`: `{ method: POST, path: /api/v1/tools/{id}/run, job: { type, operation, preset? } | null, legacy? }`.++ - Its data:++ - `input`: a JSON Schema, embedded or `{ $ref }` to `GET /api/v1/tools/:id/schema#/$defs/input`.++ - `files`: `{ min, max, accept, maxBytes }` or null.++ - `output`: `{ kind: json|file|files|text, schema?, mime? }`.++ - `limits`: `timeoutMs`, and optionally `maxDurationSec`, `maxPixels`, `maxPages`, `maxInputBytes`,++ `perTargetPerMinute`.++ - Access and cost:++ - `auth`: `{ anonymous, capability: tools.tool.run | tools.net.probe }`.++ - `quotaClass` and `cost` (the relative weight quotas count).++ - `egress`: the server fetches a host the caller chose.++ - `hosts` and `docs`.++++ The schema enforces these rules:++ - `api: false` has `run: null`, and `api: true` has a run and an input schema.++ - Only job tools name a job, and an API job tool always does.++ - File output through the API comes from a job.++ - A `tools.net.probe` tool fetches and is never anonymous.++ - An anonymous egress tool declares `limits.perTargetPerMinute`.++ - A client tool never fetches.++ - `unavailable` needs `statusReason`.++ - JSON output has a schema.++- `tools.tool-list@1`: the `GET /api/v1/tools` answer (`tools`, `count`, `updated_at`, optional `families`).++- `tools.run-request@1`: the body of `POST /api/v1/tools/{id}/run`, `{ input, files?, wait_ms?, idempotency_key? }`.++ It can be JSON or multipart, like the jobs API. `files` references a Media object (`{ media_id }`)++ or a result file of the caller's own job (`{ job_id, index }`), so tools can chain. The refusal++ codes are listed in the description.++- `tools.run@1`: the answer. Either `{ state: succeeded, tool, result: { data?, text?, files? }, took_ms, job? }`,++ or `{ state: failed|cancelled, tool, error, took_ms, job? }`, or `{ state: queued|running, tool, job, location }`++ (202).++- `tools.job@1`: the jobs API's view of a job, read from `apps/_shared/jobs` `system.view()` today.++ It carries state, progress, attempts, times, `expires_at`, result files (`storage`, `media`, `url`),++ error, retry links and references. It also has an optional `tool`, set when the job came from a++ run. `tools.job-request@1` is the body of `POST /api/v1/jobs`.++- `contracts.tools` (`lib/tools.js`) adds three helpers:++ - `checkDescriptor(d)` applies the schema plus the rules that depend on the id: `run.path` is the++ tool's own, `$ref`s point at its own schema, `files.min <= files.max`, `legacy` never lists the++ run API, and the quota class has no tier word.++ - `checkList(list)` checks every descriptor, that ids and hosts are unique, and that counts are right.++ - `jobInput(d, input)` returns `{ ...input, ...preset, tool: operation }`.++++**Capabilities**++++- These are `planned` until Tools serves their routes:++ - `tools.tool.read`: public, quota class `tools-read`. `GET /api/v1/tools`, `GET /api/v1/tools/:id`++ and `GET /api/v1/tools/:id/schema`.++ - `tools.tool.run`: public, quota class `tools-run`, `tools.run-request@1` → `tools.run@1`.++ `POST /api/v1/tools/:id/run`.++ - `tools.net.probe`: **partner**, quota class `tools-probe`. Network grants it only through a++ staff-set project allowance, never a default one. It covers port, ping, traceroute, mtr, latency++ and bulk header and TLS checks through the API.++- `tools.job.create` adds `POST /api/v1/jobs/:id/retry` and `PUT|DELETE /api/v1/jobs/:id/references/:ref`,++ and declares `tools.job-request@1` → `tools.job@1`. `tools.job.read` and `tools.job.cancel` declare++ `tools.job@1`.++++**Manifests and statuses**++++- Tools 0.4.0:++ - lists the three new capabilities;++ - gains `ready: /api/ready`;++ - its notes now say that job results are in Media and job events reach OpenVibe.Events in++ production, that the gateway has no jobs routes yet, and what ADR-027 plans.++- `tools.job.created|started|succeeded|failed` are `active` (they were `planned`), because Tools++ publishes them. Their descriptions no longer say planned.++++**Tests.** `test/run.js` checks:++- every descriptor fixture with `checkDescriptor`;++- that a run endpoint exists exactly when `api` is true;++- that only job tools name a job;++- the egress, probe and client rules, and tier-free quota classes;++- that the run capabilities and the descriptor's `auth.capability` agree;++- that a run's result files are exactly the job's, and that runs and jobs share the Idempotency-Key++ rule and the job type pattern;++- that the `tools.job.succeeded` event carries a subset of the job view;++- the list's counts, ids and hosts.++## 0.32.0 — 2026-09-23Additive: `compat.js` reports no breaking change against v0.31.0.@@@ -2,7 +2,7 @@> Machine-readable contracts for the whole OpenVibe network.--**Status:** alpha, v0.30.0 (first released in Wave 1). 74 schemas (53 of them event payloads), 30 service manifests, 170 capability manifests and 6 namespace manifests. 23 repositories run `openvibe-contracts-check` in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Deployed consumers pin different tags (Games v0.8.0, the rest v0.11.0 to v0.29.0); Network, which serves the registry, pins v0.28.0. No N/N-1 compatibility fixtures exist yet.++**Status:** alpha, v0.33.0 (first released in Wave 1). 95 schemas (66 of them event payloads), 30 service manifests, 176 capability manifests and 6 namespace manifests. 23 repositories run `openvibe-contracts-check` in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Deployed consumers pin different tags (Games v0.8.0, the rest v0.11.0 to v0.29.0); Network, which serves the registry, pins v0.28.0. No N/N-1 compatibility fixtures exist yet.**Plan:** OpenVibe Development Roadmap, Wave 1 (implementation plan rev 3, §3.1 and §18.11).**License:** AGPL-3.0 (same as every OpenVibe service).@@@ -60,7 +60,7 @@ TypeScript types: `generated/typescript/index.d.ts` (`"types"` in package.json).| `events.event-envelope` | events | durable event shape for OpenVibe.Events (Wave 3) || `media.media-ref` | media | `med_<ULID>` (Wave 4) or transitional `legacy:<app>:<kind>:<id>` |--Manifests: `manifests/services/` (30 services as of v0.30.0: 23 `alpha`, 1 `beta`, 5 `stable` and 1 `placeholder`, realtime; SDK and Shared are released libraries and Examples a repository with CI, none of them a runtime), `manifests/capabilities/` (173 capabilities as of v0.32.0; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Where a service is reachable today (public, loopback only, library) is Network's observed overlay (`server/registry/exposure.js` there); manifest notes say whether a service is deployed and launched publicly.++Manifests: `manifests/services/` (30 services as of v0.30.0: 23 `alpha`, 1 `beta`, 5 `stable` and 1 `placeholder`, realtime; SDK and Shared are released libraries and Examples a repository with CI, none of them a runtime), `manifests/capabilities/` (176 capabilities as of v0.33.0; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Where a service is reachable today (public, loopback only, library) is Network's observed overlay (`server/registry/exposure.js` there); manifest notes say whether a service is deployed and launched publicly.**Ids.** Subjects use prefixed ULIDs: `usr_`, `gst_`, `app_`, `mod_`. Services and system actors use slugs (`live`, `media`). Events use `evt_` and Media objects `med_`.@@@ -84,7 +84,7 @@ Three `public` capabilities let a developer app (ADR-014) use OpenVibe.Events wi## Event payload contracts (v0.30)--Every event type has a payload contract, named after the type. The envelope's `version` is the contract's major, and the file is `contracts/events/payloads/<event_type>.v<version>.json`. The owner is the producing service, whose manifest lists the type in `eventsProduced`. Contracts with status `planned` describe events the owner does not emit yet (`ai.run.*`, `tools.job.*`).++Every event type has a payload contract, named after the type. The envelope's `version` is the contract's major, and the file is `contracts/events/payloads/<event_type>.v<version>.json`. The owner is the producing service, whose manifest lists the type in `eventsProduced`. Contracts with status `planned` describe events the owner does not emit yet (`ai.run.*`). `tools.job.*` are `active` since v0.33.0, because Tools publishes them.```jsif (env.payload.redacted === true) return; // a tombstone (events.tombstone-payload@1)@@@ -124,6 +124,31 @@ One resolver answers "which channel, and whose, is this?" for channel, stream, VThe first input that resolves decides. Every other input that resolves must name the same channel, or the answer is `unresolved` with reason `conflict`. A clip belongs to the clipped channel, never to the clipper. A display name never resolves anything: a request holding only one is answered `unresolved`, `display_name_only`.++## Tools platform API (v0.33)++++Every tool on openvibe.tools gets a descriptor, `tools.tool@1` (ADR-027). The page, the run API,++openvibe-sdk and the docs all read it. It says:++++- where the tool runs (`execution`: `client` | `sync` | `job`);++- whether the API exposes it (`api`), and how to call it (`run`);++- its input schema, files, output, limits and auth;++- its quota class and cost;++- whether it fetches hosts the caller chose (`egress`).++++| Route (openvibe.tools) | Capability | Contract |++|---|---|---|++| `GET /api/v1/tools`, `/api/v1/tools/:id`, `/api/v1/tools/:id/schema` | `tools.tool.read` (public) | `tools.tool-list@1`, `tools.tool@1` |++| `POST /api/v1/tools/:id/run` | `tools.tool.run` (public); probes need `tools.net.probe` (partner) | `tools.run-request@1` → `tools.run@1` |++| `/api/v1/jobs…` (img, audio, docs; the gateway later) | `tools.job.create`, `.read`, `.cancel` | `tools.job-request@1` → `tools.job@1` |++++A run is answered inline, or with its job (202 + `Location`) while a job tool is still working.++Callers are tiered: anonymous < session < user < app/service. Quotas count each tool's `cost` within++its `quotaClass`. yt is page-only (`api: false`). The registry routes and the run route are `planned`++until Tools serves them.++++`contracts.tools.checkDescriptor(d)` and `checkList(list)` apply the rules that JSON Schema cannot++express, such as `run.path` being the tool's own. `jobInput(d, input)` builds a job tool's job input.++## Versioning and compatibility- A contract id is permanent. Minor versions only add optional fields.nnew file mode 100644@@@ -0,0 +1,201 @@++# ADR-027: Tools platform API++++**Status:** Accepted 2026-09-23. Contracts in openvibe-contracts v0.33.0; the registry and run routes++are `planned` until OpenVibe.Tools serves them.++++## Context and current evidence++++The owner asked on 2026-09-23 for every tool to be open, modular, reachable through an API and the++SDK, efficient, protected against abuse, and used across the other repositories. The survey of++OpenVibe.Tools that day found:++++- **169 public tools in 9 families**, listed by the gateway's catalogue (`GET /api/catalog.json`,++ re-served by Network). Each tool is described in three places that nobody reconciles: the gateway++ copy, the satellite's `domain-map.js` and the satellite's `tools/index.js`.++- **Only the jobs API is uniform.** `POST /api/v1/jobs` and the routes under it run only on the img,++ audio and docs satellites (`apps/_shared/jobs/http.js`). Everything else has its own shape:++ `POST /api/process` and `/api/process/direct` answer `{ success, download }` or `{ error }`,++ `GET /api/net/<endpoint>/:target` answers `{ ok, … }`, and the `/api/dev/*`, yt and maps routes++ each do something different. No endpoint states its input schema, its limits or how it runs.++- **Browser-only tools have no API.** About 70 dev and text tools exist only as page JavaScript.++- **Abuse controls are uneven.** Rate limits are in memory and per IP only. The limiter runs before++ sign-in is read, so the signed-in tier never applies. Every first-party server caller shares++ 127.0.0.1. The port tool accepts 20 ports a request at 60 requests a minute with no per-target++ limit. myip trusts the first `X-Forwarded-For` entry the client sent. Sync endpoints skip the job++ caps. Nothing records abuse.++- **Some tools promise more than they do.** Six net tools have no implementation. traceroute, mtr++ and latency are a TCP ping. Protect PDF does not encrypt, and PDF to image returns a placeholder.++- **In the contracts,** `tools.job.*` did not list retry or references and had no schemas, and++ nothing enforced a quota class.++++## Decision++++### One descriptor per tool, one run API++++- **`tools.tool@1` describes each tool.** It gives `id` (the catalogue id), `family`, `name`,++ `summary`, `status` (`stable` | `beta` | `preview` | `unavailable`, and `statusReason` when a tool++ is unavailable), `execution` (`client` | `sync` | `job`), `api`, `run`, `input` (JSON Schema),++ `files`, `output`, `limits`, `auth`, `quotaClass`, `cost`, `egress`, `hosts` and `docs`. Tools++ builds the descriptors from each tool's own code, which retires the three copies. The page, the++ run API, `openvibe-sdk/tools`, the OpenAPI document and the docs all read them.++- **Registry routes** (capability `tools.tool.read`, public). `GET /api/v1/tools` answers++ `tools.tool-list@1`, with schemas as `$ref`. `GET /api/v1/tools/:id` answers the descriptor with++ its schemas embedded. `GET /api/v1/tools/:id/schema` answers++ `{ $schema, $id, $defs: { input, output } }`, which the `$ref`s point at.++- **Run route** (capability `tools.tool.run`, public). `POST /api/v1/tools/:id/run` takes++ `tools.run-request@1`, as JSON or as multipart with the same parts the jobs API takes. It answers++ `tools.run@1`:++ - `200` when the run finished: `succeeded` with `result`, or `failed` or `cancelled` with the++ tool's own `error`;++ - `202` with `Location` while a job is still `queued` or `running`, after waiting up to++ `wait_ms`.++++ Refusals before the tool runs are problem+json. `run-request.v1.json` lists the codes.++- **Execution modes.** A `sync` tool is answered inline. A `job` tool's run submits a job of++ `run.job.type` whose input is `{ ...input, ...run.job.preset, tool: run.job.operation }`. The++ job, its events, its files, retry and references are the existing jobs API (`tools.job@1`).++ Output that is a file always comes from a job, so a result always has a download URL, an owner++ and an expiry. Inputs can reference files that already exist: a Media object the caller may++ read, or a result file of the caller's own job. That lets one tool's output feed the next++ without a download.++- **The gateway fronts every job route** under `/api/v1/jobs` (a facade over the satellites), so++ callers and the SDK need one base URL.++++### Browser-only tools may get server engines++++A pure transform whose engine also runs in Node gets an opt-in server engine for the API: JSON,++minify and beautify, case, slug, hash, base64 and the like. Its descriptor then says++`execution: client` and `api: true`. The page stays browser-only, so "nothing leaves your browser"++stays true for people using the page. A tool without such an engine says `api: false`.++++### YouTube downloader is page-only++++yt stays a page for people. It is not in the run API or the SDK, because of YouTube's terms and the++legal exposure. Its descriptor says `api: false` and `run: null`.++++### Callers, tiers and quotas++++- **Tiers, lowest first:** `anonymous < session < user < app/service`.++ - **anonymous:** no token and no session. Keyed by IP, IPv6 by its /64.++ - **session:** the browser's `ov_tools_jobs` cookie.++ - **user:** a Network user token or the `ov_token` cookie.++ - **app/service:** a Network client-credentials token for audience `openvibe.tools`.++- **Anonymous API use is allowed:** the tools are open. It gets the lowest allowance. A tool with++ `auth.anonymous: false` (the heavy job types) needs at least a browser session or a token.++ Sandbox app tokens keep their small allowance (ADR-014).++- **Quotas count weight, not requests.** Each run counts its descriptor's `cost` against its++ `quotaClass` (`tools-run`, `tools-job`, `tools-fetch`, `tools-probe`…), with an allowance per++ tier. A 429 carries `Retry-After`, which openvibe-sdk honours.++- **A class name describes the work, never a tier or a price.** Paid tiers are not built now, and++ when they come they add allowances, not class names. `contracts.tools.checkDescriptor` refuses++ a class name containing a tier word.++- **Only one proxy hop is trusted.** Host nginx derives the client IP (`real_ip_header++ CF-Connecting-IP` over the Cloudflare ranges), and every proxied location sets `X-Real-IP`,++ `X-Forwarded-For` and `CF-Connecting-IP` from `$remote_addr`. Express `trust proxy` matches that++ one hop. A client-sent `X-Forwarded-For` entry is never trusted.++- **Challenge hook.** The guard has a no-op challenge hook, so a challenge such as Turnstile can be++ added later without changing the API. None is used now.++++### Network probes need `tools.net.probe`++++Port checks, ping, traceroute, mtr, latency and bulk header or TLS checks send traffic to a host the++caller chose. Through the API and the SDK they need the capability `tools.net.probe`, whose++visibility is **partner**. Network grants it to a developer project only when staff add it to that++project's allowance by hand, never through a default or sandbox allowance. First-party services++hold it through their own grants. People keep the probe pages, which have a per-target throttle.++++A descriptor with `egress: true` either refuses anonymous callers or declares++`limits.perTargetPerMinute`: runs per minute against one target, across all callers. A probe tool++always has `egress: true` and `auth.anonymous: false`. A client tool never fetches.++++### Abuse log++++Tools records refused and throttled runs, and runs that trip an abuse rule. Each row holds:++++- the caller as `HMAC-SHA256(IP, daily salt)`;++- the principal or user subject when there is one;++- the tool, the reason and the time.++++**No raw IP is stored anywhere.** The salt rotates daily and is never kept after its day, so an++old hash cannot be tied back to an address, not even by trying every IPv4 address. Rows are kept++**30 days**. Under ADR-021 this is pseudonymous security data, not analytics. Its only use is++acting on abuse: throttling, blocking a token or an app, reporting to the owner. It is never++joined into analytics. Subject ids may appear because acting on a token or an app needs them,++which ADR-021's analytics rule does not cover.++++### Legacy endpoints get deprecation headers++++`/api/process` (and its `/direct`, `/multi` and `/info` variants), `/api/net/*` and `/api/dev/*`++keep working. Each answer carries:++++- `Deprecation: true`++- `Sunset: Thu, 31 Dec 2026 23:59:59 GMT`++- `Link: </api/v1/tools/{id}/run>; rel="successor-version"`++++A descriptor lists these routes in `run.legacy`, which gives the mapping. Removing them after the++sunset is a later decision, informed by how much they are still used.++++### Honest status++++A tool whose engine is missing on the host has status `unavailable`: it is listed, its runs are++refused with `503 tools.tool.unavailable`, and its page says so. PDF protection becomes real++(qpdf, AES-256) and PDF to image uses pdftoppm. They stay `unavailable` until `qpdf` and++`poppler-utils` are deployed. Probe tools that only do a TCP ping describe themselves as that.++++## Alternatives considered++++- **Keep one API per satellite and document it.** Rejected: the shapes, error bodies and auth++ differ per satellite, and the SDK would need one client per tool family.++- **Jobs only, with no inline runs.** Rejected: a DNS lookup or a JSON minify would cost a job row,++ a poll and a file, for a millisecond of work.++- **Probes open to anonymous API callers.** Rejected: the port tool can already be used to scan++ third parties, and a public API makes that cheap to automate. People keep the pages.++- **The downloader in the API.** Rejected on YouTube's terms and legal exposure.++- **Raw IPs in the abuse log, deleted later.** Rejected: a daily-salted HMAC is enough to link one++ day's abuse, and it never becomes an address list.++- **A challenge (Turnstile) now.** Deferred: it needs owner setup. The hook keeps the option open.++++## Migration consequences++++In order:++++1. Contracts v0.33.0 (this ADR).++2. The Tools registry and descriptors.++3. The shared guard (tiers, quotas, semaphores, upload sniffing, ffmpeg hardening, egress++ throttles, the abuse log), first in report mode.++4. Worker isolation, so pdf-lib and sharp stop blocking the event loop.++5. The run API, the gateway jobs facade and the legacy headers.++6. Network grants and the sandbox allowance.++7. openvibe-sdk `tools` and the jobs client's retry and references.++8. Callers move over: Live kiosk page titles, Chat MP3 conversion, Community "save as paste",++ Codes docs, the Search index, the Network launcher's recent tools.++++`tools.tool.read`, `tools.tool.run` and `tools.net.probe` become `active` in the contracts release++after Tools serves their routes.++++## Rollback++++Every new route is additive. Turning the run API off leaves the pages and the legacy routes as they++were. The guard starts in report mode, and enforcement is a switch. Deprecation headers are++informational. Deleting the abuse log's table removes it completely, and it holds no raw data to++leak.++++## Acceptance tests++++- openvibe-contracts `test/run.js`:++ - every descriptor fixture passes `tools.tool@1` and `checkDescriptor`;++ - api false never has a run endpoint, and api true has its own path and an input schema;++ - only job tools name a job, and file output through the API comes from one;++ - an anonymous egress tool has a per-target throttle, and a probe is never anonymous;++ - a client tool never fetches;++ - quota classes carry no tier word;++ - a run's result files are exactly the job's;++ - the list's ids, hosts and counts are consistent.++- In Tools, a registry test runs `checkDescriptor` and `checkList` over every tool, and every++ catalogue id has a descriptor.++- Run API tests cover each refusal code, anonymous versus session versus token tiers, and a 429++ with `Retry-After`.++- A probe run without `tools.net.probe` gets 403.++- yt's run answers `404 tools.tool.not_runnable`.++- Legacy routes carry `Deprecation`, `Sunset` and `Link`.++- No raw IP appears in the abuse log, and nothing in it is older than 30 days.@@@ -30,4 +30,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each| [ADR-024](ADR-024-theme-authority.md) | Theme authority between Network and Shared | Accepted 2026-09-23 || [ADR-025](ADR-025-marketplace-scope.md) | Marketplace and commerce scope | Accepted 2026-09-23: Trade stays informational || [ADR-026](ADR-026-event-redaction.md) | Producer redaction of its own events (`payload.redacts`) and event payload contracts | Accepted and implemented 2026-09-23 (OpenVibe.Events, Chat) |--++| [ADR-027](ADR-027-tools-platform-api.md) | Tools platform API: descriptors, one run API, caller tiers, probes, abuse log, legacy sunset | Accepted 2026-09-23; contracts v0.33.0, routes planned |@@@ -1,12 +1,12 @@{"name": "openvibe-contracts",-- "version": "0.32.0",++ "version": "0.33.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-contracts",-- "version": "0.32.0",++ "version": "0.33.0","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.32.0",++ "version": "0.33.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,95 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -4,6 +4,95 @@ 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.33.0 — 2026-09-23
+
+Additive: `compat.js` reports no breaking change against v0.32.0.
+
+**Tools platform API** (new ADR-027). The tools on openvibe.tools become one registry and one run API,
+open to the SDK, with tiered, weighted quotas.
+
+- `tools.tool@1`: the **tool descriptor**, one per catalogue tool.
+ - Identity: `id`, `family`, `name`, `summary`.
+ - `status`: `stable` | `beta` | `preview` | `unavailable`, with `statusReason`.
+ - How it runs:
+ - `execution`: `client` | `sync` | `job`.
+ - `api`: whether the run API exposes the tool.
+ - `run`: `{ method: POST, path: /api/v1/tools/{id}/run, job: { type, operation, preset? } | null, legacy? }`.
+ - Its data:
+ - `input`: a JSON Schema, embedded or `{ $ref }` to `GET /api/v1/tools/:id/schema#/$defs/input`.
+ - `files`: `{ min, max, accept, maxBytes }` or null.
+ - `output`: `{ kind: json|file|files|text, schema?, mime? }`.
+ - `limits`: `timeoutMs`, and optionally `maxDurationSec`, `maxPixels`, `maxPages`, `maxInputBytes`,
+ `perTargetPerMinute`.
+ - Access and cost:
+ - `auth`: `{ anonymous, capability: tools.tool.run | tools.net.probe }`.
+ - `quotaClass` and `cost` (the relative weight quotas count).
+ - `egress`: the server fetches a host the caller chose.
+ - `hosts` and `docs`.
+
+ The schema enforces these rules:
+ - `api: false` has `run: null`, and `api: true` has a run and an input schema.
+ - Only job tools name a job, and an API job tool always does.
+ - File output through the API comes from a job.
+ - A `tools.net.probe` tool fetches and is never anonymous.
+ - An anonymous egress tool declares `limits.perTargetPerMinute`.
+ - A client tool never fetches.
+ - `unavailable` needs `statusReason`.
+ - JSON output has a schema.
+- `tools.tool-list@1`: the `GET /api/v1/tools` answer (`tools`, `count`, `updated_at`, optional `families`).
+- `tools.run-request@1`: the body of `POST /api/v1/tools/{id}/run`, `{ input, files?, wait_ms?, idempotency_key? }`.
+ It can be JSON or multipart, like the jobs API. `files` references a Media object (`{ media_id }`)
+ or a result file of the caller's own job (`{ job_id, index }`), so tools can chain. The refusal
+ codes are listed in the description.
+- `tools.run@1`: the answer. Either `{ state: succeeded, tool, result: { data?, text?, files? }, took_ms, job? }`,
+ or `{ state: failed|cancelled, tool, error, took_ms, job? }`, or `{ state: queued|running, tool, job, location }`
+ (202).
+- `tools.job@1`: the jobs API's view of a job, read from `apps/_shared/jobs` `system.view()` today.
+ It carries state, progress, attempts, times, `expires_at`, result files (`storage`, `media`, `url`),
+ error, retry links and references. It also has an optional `tool`, set when the job came from a
+ run. `tools.job-request@1` is the body of `POST /api/v1/jobs`.
+- `contracts.tools` (`lib/tools.js`) adds three helpers:
+ - `checkDescriptor(d)` applies the schema plus the rules that depend on the id: `run.path` is the
+ tool's own, `$ref`s point at its own schema, `files.min <= files.max`, `legacy` never lists the
+ run API, and the quota class has no tier word.
+ - `checkList(list)` checks every descriptor, that ids and hosts are unique, and that counts are right.
+ - `jobInput(d, input)` returns `{ ...input, ...preset, tool: operation }`.
+
+**Capabilities**
+
+- These are `planned` until Tools serves their routes:
+ - `tools.tool.read`: public, quota class `tools-read`. `GET /api/v1/tools`, `GET /api/v1/tools/:id`
+ and `GET /api/v1/tools/:id/schema`.
+ - `tools.tool.run`: public, quota class `tools-run`, `tools.run-request@1` → `tools.run@1`.
+ `POST /api/v1/tools/:id/run`.
+ - `tools.net.probe`: **partner**, quota class `tools-probe`. Network grants it only through a
+ staff-set project allowance, never a default one. It covers port, ping, traceroute, mtr, latency
+ and bulk header and TLS checks through the API.
+- `tools.job.create` adds `POST /api/v1/jobs/:id/retry` and `PUT|DELETE /api/v1/jobs/:id/references/:ref`,
+ and declares `tools.job-request@1` → `tools.job@1`. `tools.job.read` and `tools.job.cancel` declare
+ `tools.job@1`.
+
+**Manifests and statuses**
+
+- Tools 0.4.0:
+ - lists the three new capabilities;
+ - gains `ready: /api/ready`;
+ - its notes now say that job results are in Media and job events reach OpenVibe.Events in
+ production, that the gateway has no jobs routes yet, and what ADR-027 plans.
+- `tools.job.created|started|succeeded|failed` are `active` (they were `planned`), because Tools
+ publishes them. Their descriptions no longer say planned.
+
+**Tests.** `test/run.js` checks:
+- every descriptor fixture with `checkDescriptor`;
+- that a run endpoint exists exactly when `api` is true;
+- that only job tools name a job;
+- the egress, probe and client rules, and tier-free quota classes;
+- that the run capabilities and the descriptor's `auth.capability` agree;
+- that a run's result files are exactly the job's, and that runs and jobs share the Idempotency-Key
+ rule and the job type pattern;
+- that the `tools.job.succeeded` event carries a subset of the job view;
+- the list's counts, ids and hosts.
+
## 0.32.0 — 2026-09-23
## 0.32.0 — 2026-09-23
Additive: `compat.js` reports no breaking change against v0.31.0.
Additive: `compat.js` reports no breaking change against v0.31.0.
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
> Machine-readable contracts for the whole OpenVibe network.
> Machine-readable contracts for the whole OpenVibe network.
-**Status:** alpha, v0.30.0 (first released in Wave 1). 74 schemas (53 of them event payloads), 30 service manifests, 170 capability manifests and 6 namespace manifests. 23 repositories run `openvibe-contracts-check` in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Deployed consumers pin different tags (Games v0.8.0, the rest v0.11.0 to v0.29.0); Network, which serves the registry, pins v0.28.0. No N/N-1 compatibility fixtures exist yet.
+**Status:** alpha, v0.33.0 (first released in Wave 1). 95 schemas (66 of them event payloads), 30 service manifests, 176 capability manifests and 6 namespace manifests. 23 repositories run `openvibe-contracts-check` in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Deployed consumers pin different tags (Games v0.8.0, the rest v0.11.0 to v0.29.0); Network, which serves the registry, pins v0.28.0. No N/N-1 compatibility fixtures exist yet.
**Plan:** OpenVibe Development Roadmap, Wave 1 (implementation plan rev 3, §3.1 and §18.11).
**Plan:** OpenVibe Development Roadmap, Wave 1 (implementation plan rev 3, §3.1 and §18.11).
**License:** AGPL-3.0 (same as every OpenVibe service).
**License:** AGPL-3.0 (same as every OpenVibe service).
@@ -60,7 +60,7 @@ TypeScript types: `generated/typescript/index.d.ts` (`"types"` in package.json).
@@ -60,7 +60,7 @@ TypeScript types: `generated/typescript/index.d.ts` (`"types"` in package.json).
| `events.event-envelope` | events | durable event shape for OpenVibe.Events (Wave 3) |
| `events.event-envelope` | events | durable event shape for OpenVibe.Events (Wave 3) |
| `media.media-ref` | media | `med_<ULID>` (Wave 4) or transitional `legacy:<app>:<kind>:<id>` |
| `media.media-ref` | media | `med_<ULID>` (Wave 4) or transitional `legacy:<app>:<kind>:<id>` |
-Manifests: `manifests/services/` (30 services as of v0.30.0: 23 `alpha`, 1 `beta`, 5 `stable` and 1 `placeholder`, realtime; SDK and Shared are released libraries and Examples a repository with CI, none of them a runtime), `manifests/capabilities/` (173 capabilities as of v0.32.0; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Where a service is reachable today (public, loopback only, library) is Network's observed overlay (`server/registry/exposure.js` there); manifest notes say whether a service is deployed and launched publicly.
+Manifests: `manifests/services/` (30 services as of v0.30.0: 23 `alpha`, 1 `beta`, 5 `stable` and 1 `placeholder`, realtime; SDK and Shared are released libraries and Examples a repository with CI, none of them a runtime), `manifests/capabilities/` (176 capabilities as of v0.33.0; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Where a service is reachable today (public, loopback only, library) is Network's observed overlay (`server/registry/exposure.js` there); manifest notes say whether a service is deployed and launched publicly.
**Ids.** Subjects use prefixed ULIDs: `usr_`, `gst_`, `app_`, `mod_`. Services and system actors use slugs (`live`, `media`). Events use `evt_` and Media objects `med_`.
**Ids.** Subjects use prefixed ULIDs: `usr_`, `gst_`, `app_`, `mod_`. Services and system actors use slugs (`live`, `media`). Events use `evt_` and Media objects `med_`.
@@ -84,7 +84,7 @@ Three `public` capabilities let a developer app (ADR-014) use OpenVibe.Events wi
@@ -84,7 +84,7 @@ Three `public` capabilities let a developer app (ADR-014) use OpenVibe.Events wi
## Event payload contracts (v0.30)
## Event payload contracts (v0.30)
-Every event type has a payload contract, named after the type. The envelope's `version` is the contract's major, and the file is `contracts/events/payloads/<event_type>.v<version>.json`. The owner is the producing service, whose manifest lists the type in `eventsProduced`. Contracts with status `planned` describe events the owner does not emit yet (`ai.run.*`, `tools.job.*`).
+Every event type has a payload contract, named after the type. The envelope's `version` is the contract's major, and the file is `contracts/events/payloads/<event_type>.v<version>.json`. The owner is the producing service, whose manifest lists the type in `eventsProduced`. Contracts with status `planned` describe events the owner does not emit yet (`ai.run.*`). `tools.job.*` are `active` since v0.33.0, because Tools publishes them.
```js
```js
if (env.payload.redacted === true) return; // a tombstone (events.tombstone-payload@1)
if (env.payload.redacted === true) return; // a tombstone (events.tombstone-payload@1)
@@ -124,6 +124,31 @@ One resolver answers "which channel, and whose, is this?" for channel, stream, V
@@ -124,6 +124,31 @@ One resolver answers "which channel, and whose, is this?" for channel, stream, V
The first input that resolves decides. Every other input that resolves must name the same channel, or the answer is `unresolved` with reason `conflict`. A clip belongs to the clipped channel, never to the clipper. A display name never resolves anything: a request holding only one is answered `unresolved`, `display_name_only`.
The first input that resolves decides. Every other input that resolves must name the same channel, or the answer is `unresolved` with reason `conflict`. A clip belongs to the clipped channel, never to the clipper. A display name never resolves anything: a request holding only one is answered `unresolved`, `display_name_only`.
+## Tools platform API (v0.33)
+
+Every tool on openvibe.tools gets a descriptor, `tools.tool@1` (ADR-027). The page, the run API,
+openvibe-sdk and the docs all read it. It says:
+
+- where the tool runs (`execution`: `client` | `sync` | `job`);
+- whether the API exposes it (`api`), and how to call it (`run`);
+- its input schema, files, output, limits and auth;
+- its quota class and cost;
+- whether it fetches hosts the caller chose (`egress`).
+
+| Route (openvibe.tools) | Capability | Contract |
+|---|---|---|
+| `GET /api/v1/tools`, `/api/v1/tools/:id`, `/api/v1/tools/:id/schema` | `tools.tool.read` (public) | `tools.tool-list@1`, `tools.tool@1` |
+| `POST /api/v1/tools/:id/run` | `tools.tool.run` (public); probes need `tools.net.probe` (partner) | `tools.run-request@1` → `tools.run@1` |
+| `/api/v1/jobs…` (img, audio, docs; the gateway later) | `tools.job.create`, `.read`, `.cancel` | `tools.job-request@1` → `tools.job@1` |
+
+A run is answered inline, or with its job (202 + `Location`) while a job tool is still working.
+Callers are tiered: anonymous < session < user < app/service. Quotas count each tool's `cost` within
+its `quotaClass`. yt is page-only (`api: false`). The registry routes and the run route are `planned`
+until Tools serves them.
+
+`contracts.tools.checkDescriptor(d)` and `checkList(list)` apply the rules that JSON Schema cannot
+express, such as `run.path` being the tool's own. `jobInput(d, input)` builds a job tool's job input.
+
## Versioning and compatibility
## Versioning and compatibility
- A contract id is permanent. Minor versions only add optional fields.
- A contract id is permanent. Minor versions only add optional fields.
new file mode 100644
new file mode 100644
@@ -0,0 +1,201 @@
@@ -0,0 +1,201 @@
+# ADR-027: Tools platform API
+
+**Status:** Accepted 2026-09-23. Contracts in openvibe-contracts v0.33.0; the registry and run routes
+are `planned` until OpenVibe.Tools serves them.
+
+## Context and current evidence
+
+The owner asked on 2026-09-23 for every tool to be open, modular, reachable through an API and the
+SDK, efficient, protected against abuse, and used across the other repositories. The survey of
+OpenVibe.Tools that day found:
+
+- **169 public tools in 9 families**, listed by the gateway's catalogue (`GET /api/catalog.json`,
+ re-served by Network). Each tool is described in three places that nobody reconciles: the gateway
+ copy, the satellite's `domain-map.js` and the satellite's `tools/index.js`.
+- **Only the jobs API is uniform.** `POST /api/v1/jobs` and the routes under it run only on the img,
+ audio and docs satellites (`apps/_shared/jobs/http.js`). Everything else has its own shape:
+ `POST /api/process` and `/api/process/direct` answer `{ success, download }` or `{ error }`,
+ `GET /api/net/<endpoint>/:target` answers `{ ok, … }`, and the `/api/dev/*`, yt and maps routes
+ each do something different. No endpoint states its input schema, its limits or how it runs.
+- **Browser-only tools have no API.** About 70 dev and text tools exist only as page JavaScript.
+- **Abuse controls are uneven.** Rate limits are in memory and per IP only. The limiter runs before
+ sign-in is read, so the signed-in tier never applies. Every first-party server caller shares
+ 127.0.0.1. The port tool accepts 20 ports a request at 60 requests a minute with no per-target
+ limit. myip trusts the first `X-Forwarded-For` entry the client sent. Sync endpoints skip the job
+ caps. Nothing records abuse.
+- **Some tools promise more than they do.** Six net tools have no implementation. traceroute, mtr
+ and latency are a TCP ping. Protect PDF does not encrypt, and PDF to image returns a placeholder.
+- **In the contracts,** `tools.job.*` did not list retry or references and had no schemas, and
+ nothing enforced a quota class.
+
+## Decision
+
+### One descriptor per tool, one run API
+
+- **`tools.tool@1` describes each tool.** It gives `id` (the catalogue id), `family`, `name`,
+ `summary`, `status` (`stable` | `beta` | `preview` | `unavailable`, and `statusReason` when a tool
+ is unavailable), `execution` (`client` | `sync` | `job`), `api`, `run`, `input` (JSON Schema),
+ `files`, `output`, `limits`, `auth`, `quotaClass`, `cost`, `egress`, `hosts` and `docs`. Tools
+ builds the descriptors from each tool's own code, which retires the three copies. The page, the
+ run API, `openvibe-sdk/tools`, the OpenAPI document and the docs all read them.
+- **Registry routes** (capability `tools.tool.read`, public). `GET /api/v1/tools` answers
+ `tools.tool-list@1`, with schemas as `$ref`. `GET /api/v1/tools/:id` answers the descriptor with
+ its schemas embedded. `GET /api/v1/tools/:id/schema` answers
+ `{ $schema, $id, $defs: { input, output } }`, which the `$ref`s point at.
+- **Run route** (capability `tools.tool.run`, public). `POST /api/v1/tools/:id/run` takes
+ `tools.run-request@1`, as JSON or as multipart with the same parts the jobs API takes. It answers
+ `tools.run@1`:
+ - `200` when the run finished: `succeeded` with `result`, or `failed` or `cancelled` with the
+ tool's own `error`;
+ - `202` with `Location` while a job is still `queued` or `running`, after waiting up to
+ `wait_ms`.
+
+ Refusals before the tool runs are problem+json. `run-request.v1.json` lists the codes.
+- **Execution modes.** A `sync` tool is answered inline. A `job` tool's run submits a job of
+ `run.job.type` whose input is `{ ...input, ...run.job.preset, tool: run.job.operation }`. The
+ job, its events, its files, retry and references are the existing jobs API (`tools.job@1`).
+ Output that is a file always comes from a job, so a result always has a download URL, an owner
+ and an expiry. Inputs can reference files that already exist: a Media object the caller may
+ read, or a result file of the caller's own job. That lets one tool's output feed the next
+ without a download.
+- **The gateway fronts every job route** under `/api/v1/jobs` (a facade over the satellites), so
+ callers and the SDK need one base URL.
+
+### Browser-only tools may get server engines
+
+A pure transform whose engine also runs in Node gets an opt-in server engine for the API: JSON,
+minify and beautify, case, slug, hash, base64 and the like. Its descriptor then says
+`execution: client` and `api: true`. The page stays browser-only, so "nothing leaves your browser"
+stays true for people using the page. A tool without such an engine says `api: false`.
+
+### YouTube downloader is page-only
+
+yt stays a page for people. It is not in the run API or the SDK, because of YouTube's terms and the
+legal exposure. Its descriptor says `api: false` and `run: null`.
+
+### Callers, tiers and quotas
+
+- **Tiers, lowest first:** `anonymous < session < user < app/service`.
+ - **anonymous:** no token and no session. Keyed by IP, IPv6 by its /64.
+ - **session:** the browser's `ov_tools_jobs` cookie.
+ - **user:** a Network user token or the `ov_token` cookie.
+ - **app/service:** a Network client-credentials token for audience `openvibe.tools`.
+- **Anonymous API use is allowed:** the tools are open. It gets the lowest allowance. A tool with
+ `auth.anonymous: false` (the heavy job types) needs at least a browser session or a token.
+ Sandbox app tokens keep their small allowance (ADR-014).
+- **Quotas count weight, not requests.** Each run counts its descriptor's `cost` against its
+ `quotaClass` (`tools-run`, `tools-job`, `tools-fetch`, `tools-probe`…), with an allowance per
+ tier. A 429 carries `Retry-After`, which openvibe-sdk honours.
+- **A class name describes the work, never a tier or a price.** Paid tiers are not built now, and
+ when they come they add allowances, not class names. `contracts.tools.checkDescriptor` refuses
+ a class name containing a tier word.
+- **Only one proxy hop is trusted.** Host nginx derives the client IP (`real_ip_header
+ CF-Connecting-IP` over the Cloudflare ranges), and every proxied location sets `X-Real-IP`,
+ `X-Forwarded-For` and `CF-Connecting-IP` from `$remote_addr`. Express `trust proxy` matches that
+ one hop. A client-sent `X-Forwarded-For` entry is never trusted.
+- **Challenge hook.** The guard has a no-op challenge hook, so a challenge such as Turnstile can be
+ added later without changing the API. None is used now.
+
+### Network probes need `tools.net.probe`
+
+Port checks, ping, traceroute, mtr, latency and bulk header or TLS checks send traffic to a host the
+caller chose. Through the API and the SDK they need the capability `tools.net.probe`, whose
+visibility is **partner**. Network grants it to a developer project only when staff add it to that
+project's allowance by hand, never through a default or sandbox allowance. First-party services
+hold it through their own grants. People keep the probe pages, which have a per-target throttle.
+
+A descriptor with `egress: true` either refuses anonymous callers or declares
+`limits.perTargetPerMinute`: runs per minute against one target, across all callers. A probe tool
+always has `egress: true` and `auth.anonymous: false`. A client tool never fetches.
+
+### Abuse log
+
+Tools records refused and throttled runs, and runs that trip an abuse rule. Each row holds:
+
+- the caller as `HMAC-SHA256(IP, daily salt)`;
+- the principal or user subject when there is one;
+- the tool, the reason and the time.
+
+**No raw IP is stored anywhere.** The salt rotates daily and is never kept after its day, so an
+old hash cannot be tied back to an address, not even by trying every IPv4 address. Rows are kept
+**30 days**. Under ADR-021 this is pseudonymous security data, not analytics. Its only use is
+acting on abuse: throttling, blocking a token or an app, reporting to the owner. It is never
+joined into analytics. Subject ids may appear because acting on a token or an app needs them,
+which ADR-021's analytics rule does not cover.
+
+### Legacy endpoints get deprecation headers
+
+`/api/process` (and its `/direct`, `/multi` and `/info` variants), `/api/net/*` and `/api/dev/*`
+keep working. Each answer carries:
+
+- `Deprecation: true`
+- `Sunset: Thu, 31 Dec 2026 23:59:59 GMT`
+- `Link: </api/v1/tools/{id}/run>; rel="successor-version"`
+
+A descriptor lists these routes in `run.legacy`, which gives the mapping. Removing them after the
+sunset is a later decision, informed by how much they are still used.
+
+### Honest status
+
+A tool whose engine is missing on the host has status `unavailable`: it is listed, its runs are
+refused with `503 tools.tool.unavailable`, and its page says so. PDF protection becomes real
+(qpdf, AES-256) and PDF to image uses pdftoppm. They stay `unavailable` until `qpdf` and
+`poppler-utils` are deployed. Probe tools that only do a TCP ping describe themselves as that.
+
+## Alternatives considered
+
+- **Keep one API per satellite and document it.** Rejected: the shapes, error bodies and auth
+ differ per satellite, and the SDK would need one client per tool family.
+- **Jobs only, with no inline runs.** Rejected: a DNS lookup or a JSON minify would cost a job row,
+ a poll and a file, for a millisecond of work.
+- **Probes open to anonymous API callers.** Rejected: the port tool can already be used to scan
+ third parties, and a public API makes that cheap to automate. People keep the pages.
+- **The downloader in the API.** Rejected on YouTube's terms and legal exposure.
+- **Raw IPs in the abuse log, deleted later.** Rejected: a daily-salted HMAC is enough to link one
+ day's abuse, and it never becomes an address list.
+- **A challenge (Turnstile) now.** Deferred: it needs owner setup. The hook keeps the option open.
+
+## Migration consequences
+
+In order:
+
+1. Contracts v0.33.0 (this ADR).
+2. The Tools registry and descriptors.
+3. The shared guard (tiers, quotas, semaphores, upload sniffing, ffmpeg hardening, egress
+ throttles, the abuse log), first in report mode.
+4. Worker isolation, so pdf-lib and sharp stop blocking the event loop.
+5. The run API, the gateway jobs facade and the legacy headers.
+6. Network grants and the sandbox allowance.
+7. openvibe-sdk `tools` and the jobs client's retry and references.
+8. Callers move over: Live kiosk page titles, Chat MP3 conversion, Community "save as paste",
+ Codes docs, the Search index, the Network launcher's recent tools.
+
+`tools.tool.read`, `tools.tool.run` and `tools.net.probe` become `active` in the contracts release
+after Tools serves their routes.
+
+## Rollback
+
+Every new route is additive. Turning the run API off leaves the pages and the legacy routes as they
+were. The guard starts in report mode, and enforcement is a switch. Deprecation headers are
+informational. Deleting the abuse log's table removes it completely, and it holds no raw data to
+leak.
+
+## Acceptance tests
+
+- openvibe-contracts `test/run.js`:
+ - every descriptor fixture passes `tools.tool@1` and `checkDescriptor`;
+ - api false never has a run endpoint, and api true has its own path and an input schema;
+ - only job tools name a job, and file output through the API comes from one;
+ - an anonymous egress tool has a per-target throttle, and a probe is never anonymous;
+ - a client tool never fetches;
+ - quota classes carry no tier word;
+ - a run's result files are exactly the job's;
+ - the list's ids, hosts and counts are consistent.
+- In Tools, a registry test runs `checkDescriptor` and `checkList` over every tool, and every
+ catalogue id has a descriptor.
+- Run API tests cover each refusal code, anonymous versus session versus token tiers, and a 429
+ with `Retry-After`.
+- A probe run without `tools.net.probe` gets 403.
+- yt's run answers `404 tools.tool.not_runnable`.
+- Legacy routes carry `Deprecation`, `Sunset` and `Link`.
+- No raw IP appears in the abuse log, and nothing in it is older than 30 days.
@@ -30,4 +30,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
@@ -30,4 +30,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
| [ADR-024](ADR-024-theme-authority.md) | Theme authority between Network and Shared | Accepted 2026-09-23 |
| [ADR-024](ADR-024-theme-authority.md) | Theme authority between Network and Shared | Accepted 2026-09-23 |
| [ADR-025](ADR-025-marketplace-scope.md) | Marketplace and commerce scope | Accepted 2026-09-23: Trade stays informational |
| [ADR-025](ADR-025-marketplace-scope.md) | Marketplace and commerce scope | Accepted 2026-09-23: Trade stays informational |
| [ADR-026](ADR-026-event-redaction.md) | Producer redaction of its own events (`payload.redacts`) and event payload contracts | Accepted and implemented 2026-09-23 (OpenVibe.Events, Chat) |
| [ADR-026](ADR-026-event-redaction.md) | Producer redaction of its own events (`payload.redacts`) and event payload contracts | Accepted and implemented 2026-09-23 (OpenVibe.Events, Chat) |
-
+| [ADR-027](ADR-027-tools-platform-api.md) | Tools platform API: descriptors, one run API, caller tiers, probes, abuse log, legacy sunset | Accepted 2026-09-23; contracts v0.33.0, routes planned |
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.32.0",
+ "version": "0.33.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.32.0",
+ "version": "0.33.0",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.32.0",
+ "version": "0.33.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",