openvibers/openvibe.contracts Commits
ADR-044 (Actor runtime) and the planned platform.task@1 contract; release 0.101.0 (#46)
Changes
@@@ -4,6 +4,25 @@ 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.101.0 — 2026-10-05++++**The task contract** (plan T17, ADR-044). Additive: one new planned contract and its ADR, no existing schema changed.++- `platform.task@1` (new, owner `network`, public, **planned**): one task at OpenVibe.Actor's router — the resource++ `POST /v1/tasks` creates and `GET /v1/tasks/{id}` reads, modelled on Run's `platform.job@1`. It carries `id`++ (`tsk_<ULID>`), `project_id`, `requester` (a person or an `agent` subject), the `task` text, `mode`++ (`cheapest | balanced | best | fastest | private`), `budget` (`per_task_usd`, `per_day_usd`), `state`++ (`queued | running | verifying | succeeded | failed | cancelled`), `created_at`/`finished_at`, `result`, `cost`,++ `explanation` (a chain of `platform.placement-result@1`, an escalation appending one), the SSE `progress`++ descriptor, the `cancel` record and registered `webhooks` (HTTPS only, secret by reference). Valid and invalid++ fixtures in `fixtures/platform.task/`.++- `docs/adr/ADR-044-actor-runtime.md` (new, **Proposed**; plan T17, the build-order item after the engine): the task++ contract, the adapter interface (capabilities, limits, a reviewed rate card, trust class and BYO keys), modes as++ `openvibe-sdk/placement` objectives, live signals with hysteresis and immediate failover, a `verifying` state for++ cheap-first cross-family checks and the task's own tests, and the five ADR-046 trust classes with `private`++ narrowing to OpenVibe-only. Owner is `network` because no Actor service manifest exists: OpenVibe.Actor is a product++ page (`manifests/products/openvibe.actor.json`, `noRepo`), and every other `platform.*` contract is Network's too.++- `lib/ids.js`: the `task` prefix (`tsk`), so `ids.newId('task')` mints the contract's id.++## 0.100.0 — 2026-10-05**Three small contract fixes** (plan follow-ups). Additive and widening, no contract removed.@@@ -7,7 +7,7 @@"plan": "OpenVibe Development Roadmap (combined), Wave 1; implementation plan rev 3 section 3.1","deployed": false,"deployment": "none of its own: a library and artifact repository published as release tags; services pin a tag and deploy it with their own releases",-- "contracts": "openvibe-contracts v0.100.0 (this package; the newest tag)",++ "contracts": "openvibe-contracts v0.101.0 (this package; the newest tag)","features": ["589 JSON Schemas with valid and invalid fixtures","31 service manifests with lifecycle declarations",@@@ -19,7 +19,8 @@"compatibility check against the previous tag (scripts/compat.js)","documentation-currency check (scripts/docs-currency.js)","generated estate: docs/ESTATE.md and manifests/repositories/ from the checkouts (scripts/estate.js; --check fails on drift)",-- "chat.ticket-conversation@1 and the planned chat.ticket.write capability: a per-ticket conversation keyed by ticket id and calling service, with author_kind person|agent|staff"++ "chat.ticket-conversation@1 and the planned chat.ticket.write capability: a per-ticket conversation keyed by ticket id and calling service, with author_kind person|agent|staff",++ "platform.task@1 (planned, owner network) and ADR-044: one task at OpenVibe.Actor's router with a mode, a per-task and per-day budget, result, cost and explanation, SSE progress, cancel and webhooks"],"updated": "2026-10-05"}@@@ -5619,6 +5619,16 @@"status": "active","adr": "ADR-034"},++ {++ "id": "platform.task",++ "version": "1.0.0",++ "owner": "network",++ "schema": "platform/task.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-044"++ },{"id": "events.delivery-policy","version": "1.0.0",nnew file mode 100644@@@ -0,0 +1,428 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/platform/task.v1.json",++ "title": "Task",++ "description": "platform.task@1 (PLANNED; plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /v1/tasks creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered adapters, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /v1/tasks/{id} reads the same resource. Progress streams as server-sent events from `progress.stream_url` (a task's own counter in `progress.last_seq`, as platform.job-frame@1 counts on a link); `cancel` records that a cancel was requested (POST /v1/tasks/{id}/cancel), and `webhooks` are the registered endpoints the router delivers each state change to. No Actor service exists yet: OpenVibe.Actor is a product page (manifests/products/openvibe.actor.json, noRepo) with no service manifest, so the contract is owned by `network`, like every other platform.* contract, and stays PLANNED until T17 ships. This v1 contract may grow optional fields.",++ "$comment": "Additive optional fields may extend this v1 contract. It is the task resource, not a device frame: Actor's link is HTTP/SSE where Run's is the Node WebSocket.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "project_id",++ "requester",++ "task",++ "mode",++ "budget",++ "state",++ "created_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^tsk_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "Minted by the router (tsk_<ULID>): the idempotency key of the task and the stem of its usage readings, as platform.job@1's id is for a job. Never reused."++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's."++ },++ "requester": {++ "description": "The person or agent the task belongs to: a user subject for a person, an agent subject for an agent acting under the grants its owner delegated (roadmap WS-Z2).",++ "allOf": [++ {++ "$ref": "../identity/subject-ref.v1.json"++ }++ ]++ },++ "task": {++ "type": "string",++ "minLength": 1,++ "maxLength": 100000,++ "description": "The instruction in plain words, exactly as the caller gave it. Not a backend command and not a model prompt: the planner turns it into one."++ },++ "mode": {++ "type": "string",++ "enum": [++ "cheapest",++ "balanced",++ "best",++ "fastest",++ "private"++ ],++ "default": "balanced",++ "description": "The plan's five modes, in contract spelling: Cheapest, Balanced (the default), Best, Fastest and Private are `cheapest`, `balanced`, `best`, `fastest` and `private`. A mode is the planner objective, never a fixed backend list (plan T17). `cheapest`: the lowest expected cost that still meets the task class's quality bar. `balanced` (the default): cost, quality and speed weighed together. `best`: the highest chance of a right answer first time. `fastest`: the shortest time to a checked result. `private`: only backends that keep the data on OpenVibe (first-party, and a user-owned node only when the task names it), per ADR-046."++ },++ "budget": {++ "type": "object",++ "additionalProperties": false,++ "description": "Hard limits the router never exceeds (plan T17: 'a hard limit per task and per day'). A task whose cheapest eligible backend would take it over `per_task_usd` ends failed (actor.budget.exceeded) rather than running; `per_day_usd` caps the project's tasks in one UTC day and a new task waits or fails, it never overruns.",++ "required": [++ "per_task_usd",++ "per_day_usd"++ ],++ "properties": {++ "per_task_usd": {++ "type": "number",++ "minimum": 0++ },++ "per_day_usd": {++ "type": "number",++ "minimum": 0++ }++ }++ },++ "state": {++ "type": "string",++ "enum": [++ "queued",++ "running",++ "verifying",++ "succeeded",++ "failed",++ "cancelled"++ ],++ "description": "queued: stored, waiting for the planner. running: a backend is working, or a cascade is between attempts. verifying: a result exists and a check (the task's own tests, a citation check, a postcondition) runs before it is delivered. succeeded, failed and cancelled are the end states; an end state never changes."++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "finished_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "When the task reached its end state; null before."++ },++ "result": {++ "description": "The checked deliverable (any JSON, as a platform.job@1 `result` is): for a code task a diff or branch reference, for research a brief with citations, for an action a postcondition report. Never a credential and never raw sandbox output. Null (or absent) in every state but succeeded, which the allOf below enforces."++ },++ "cost": {++ "anyOf": [++ {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "usd"++ ],++ "properties": {++ "usd": {++ "type": "number",++ "minimum": 0,++ "description": "What this task was billed, to the cent, across every attempt it made."++ },++ "free_allowance_used": {++ "type": "number",++ "minimum": 0,++ "description": "The part of `usd` covered by the project's free allowance; metered as platform.usage-sample@1."++ }++ }++ },++ {++ "type": "null"++ }++ ],++ "description": "Present once the task has run an attempt (also when it ends failed or cancelled); null while queued. One key, one bill (plan T17), through Billing (ADR-012)."++ },++ "explanation": {++ "anyOf": [++ {++ "type": "array",++ "minItems": 1,++ "items": {++ "$ref": "placement-result.v1.json"++ }++ },++ {++ "type": "null"++ }++ ],++ "description": "One platform.placement-result@1 per backend the planner tried, oldest first, an escalation appending a new one: which backend won, why and what else was considered, from openvibe-sdk/placement (ADR-046 §1). Null while queued."++ },++ "progress": {++ "anyOf": [++ {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "stream_url",++ "last_seq"++ ],++ "properties": {++ "stream_url": {++ "type": "string",++ "format": "uri",++ "description": "The text/event-stream endpoint that carries this task's progress. Each event mirrors platform.job-frame@1's job_stdout/state shape: a state change, an output chunk, or the end."++ },++ "last_seq": {++ "type": "integer",++ "minimum": 0,++ "description": "The task's own event counter, from 0 before the first event; a reconnecting reader resumes after it, as a job frame's seq counts on one link."++ },++ "events": {++ "type": "array",++ "items": {++ "type": "string",++ "enum": [++ "output",++ "state",++ "end"++ ]++ },++ "description": "The event kinds the stream delivers, run.job-stream-event@1's own three."++ }++ }++ },++ {++ "type": "null"++ }++ ],++ "description": "Streaming progress (plan T17). Null before the router opens a stream and after the end."++ },++ "cancel": {++ "anyOf": [++ {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "requested_at"++ ],++ "properties": {++ "requested_at": {++ "type": "string",++ "format": "date-time",++ "description": "When POST /v1/tasks/{id}/cancel was accepted. The running backend is stopped as its own contract allows; the state becomes cancelled and `finished_at` is set."++ },++ "requested_by": {++ "$ref": "../identity/subject-ref.v1.json"++ }++ }++ },++ {++ "type": "null"++ }++ ],++ "description": "Cancellation (plan T17): null until a cancel is requested, then it records who asked and when; the task's outcome is `state`."++ },++ "webhooks": {++ "type": "array",++ "maxItems": 8,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "url",++ "events"++ ],++ "properties": {++ "url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://",++ "description": "The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused."++ },++ "events": {++ "type": "array",++ "minItems": 1,++ "items": {++ "type": "string",++ "enum": [++ "queued",++ "running",++ "verifying",++ "succeeded",++ "failed",++ "cancelled"++ ]++ },++ "description": "The state changes delivered; the same names as `state`."++ },++ "secret_ref": {++ "type": "string",++ "pattern": "^[A-Z0-9_]+$",++ "description": "The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference."++ }++ }++ },++ "description": "Webhooks (plan T17), registered at creation. A delivery that fails is retried with backoff and never delays the task; the task's own record is the source of truth."++ }++ },++ "allOf": [++ {++ "if": {++ "required": [++ "state"++ ],++ "properties": {++ "state": {++ "const": "succeeded"++ }++ }++ },++ "then": {++ "required": [++ "result",++ "cost",++ "explanation"++ ],++ "properties": {++ "result": {++ "not": {++ "type": "null"++ }++ },++ "cost": {++ "type": "object"++ },++ "explanation": {++ "type": "array"++ }++ }++ },++ "else": {++ "properties": {++ "result": {++ "type": "null"++ }++ }++ }++ },++ {++ "if": {++ "required": [++ "state"++ ],++ "properties": {++ "state": {++ "const": "cancelled"++ }++ }++ },++ "then": {++ "required": [++ "cancel"++ ]++ }++ }++ ],++ "examples": [++ {++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"++ },++ "task": "Research five competitors of my shop and write a one-page brief with sources.",++ "mode": "balanced",++ "budget": {++ "per_task_usd": 5,++ "per_day_usd": 50++ },++ "state": "queued",++ "created_at": "2026-10-05T09:00:00.000Z",++ "webhooks": [++ {++ "url": "https://example.com/hooks/actor",++ "events": [++ "succeeded",++ "failed"++ ],++ "secret_ref": "ACTOR_WEBHOOK_SECRET"++ }++ ]++ },++ {++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"++ },++ "task": "Fix the failing thumbnail test in OpenVibe.Media and open a PR.",++ "mode": "best",++ "budget": {++ "per_task_usd": 5,++ "per_day_usd": 50++ },++ "state": "running",++ "created_at": "2026-10-05T09:00:00.000Z",++ "progress": {++ "stream_url": "https://openvibe.actor/v1/tasks/tsk_01JAB2C3D4E5F6G7H8J9K0MNPR/events",++ "last_seq": 12,++ "events": [++ "output",++ "state",++ "end"++ ]++ },++ "explanation": [++ {++ "selected": "openvibe-runtime",++ "objective": "correctness",++ "reasons": [++ "the task is code; the Codes adapter exposes a code harness"++ ],++ "candidates": [++ {++ "id": "openvibe-runtime",++ "eligible": true,++ "estimated_cost_usd": 0.06,++ "estimated_latency_ms": 180000++ }++ ],++ "decided_at": "2026-10-05T09:00:01.000Z"++ }++ ]++ },++ {++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "agent",++ "id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPT"++ },++ "task": "Check this week's outage report and summarize the causes.",++ "mode": "cheapest",++ "budget": {++ "per_task_usd": 1,++ "per_day_usd": 10++ },++ "state": "succeeded",++ "created_at": "2026-10-05T09:00:00.000Z",++ "finished_at": "2026-10-05T09:06:00.000Z",++ "result": {++ "brief": "Two outages; both were a saturated connection pool.",++ "citations": [++ "https://openvibe.status/incidents/41"++ ]++ },++ "cost": {++ "usd": 0.06,++ "free_allowance_used": 0.06++ },++ "explanation": [++ {++ "selected": "open-model",++ "objective": "cheapest",++ "reasons": [++ "research task; the checks are citations, met by the cheapest candidate"++ ],++ "candidates": [++ {++ "id": "open-model",++ "eligible": true,++ "estimated_cost_usd": 0.06,++ "estimated_latency_ms": 420000++ }++ ],++ "decided_at": "2026-10-05T09:00:01.000Z"++ }++ ],++ "cancel": null,++ "progress": null++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,192 @@++# ADR-044: The Actor runtime — the task contract, adapter interface, modes, signals, verification and trust++++**Status:** Proposed 2026-10-05 (plan track T17). This is the build-order item immediately after the engine: plan++T17's build order puts "the engine first, where it already runs — harness adapter interface in `openvibe-agents` with++the first new adapters (OpenCode, Antigravity CLI), capability-aware routing, vision inputs and a local sandbox++(headless browser + virtual desktop, screenshots)" ahead of "ADR-044 (task contract, adapter interface, modes,++signals, verification, trust)", and the engine's harness work is paused where it stands. Contract: `platform.task@1`.++Builds on ADR-034 §2 (projects and resource names), §5 (control and data plane) and §9 (metering), on ADR-046 (the++placer, its objectives and the five trust classes), on ADR-012 (one Billing authority) and on ADR-043 (Node control++levels and the local kill switch). For the owner's review.++++## Context and current evidence++++- **Plan T17** describes the product: "one API and one site where any task goes to the best agent system for it, the++ way OpenRouter does it for models — and one console where people command every OpenVibe resource in plain++ language." Its first layer is the task router: "`POST /v1/tasks` with a task, a mode and a budget → the planner++ picks the backend, runs it, verifies the result, escalates or fails over, and answers with the result, the cost and++ the explanation. Streaming progress, cancellation, webhooks."++- **The router has no contract.** `contracts/platform/` holds the Fabric's offers, requirements, placement results,++ rate cards, telemetry and usage samples, and the Run sibling's `platform.job@1` and `platform.job-frame@1`; none of++ them is a task. There is no `task.v1.json`, no route contract for `POST /v1/tasks`, and no adapter shape written++ down.++- **There is no Actor service to own it.** `manifests/services/` holds the estate's service manifests; none is++ Actor's. Actor++ exists only as a product page, `manifests/products/openvibe.actor.json`, with `relationships.noRepo: true` — a++ site description, not an authority. Every `platform.*` contract is owned by `network`, and this one follows them.++- **The engine already runs.** The plan's engine is `openvibe-agents`, the harness that runs coding, research and++ review jobs across cheap agents with review and hand-off. Plan T17 names it as where the harness adapter interface++ is built first; that work is not done, so the decisions below describe an interface the engine does not implement++ yet.++- **The pieces the router composes exist as contracts.** `openvibe-sdk/placement` is the one placer (ADR-046 §1);++ `platform.placement-result@1` is its explanation; `platform.rate-card@1`, `platform.cost-snapshot@1`,++ `platform.telemetry-sample@1` and `platform.usage-sample@1` are the cost and signal vocabulary (ADR-046 §§2–3);++ `platform.runtime-class@1`, `platform.job@1` and `platform.job-frame@1` are Run's execution shape (T14); the++ `agent` subject is already in `identity/subject-ref@1`.++- **Actor's backend classes** are fixed by the plan: "agent platform, OpenVibe runtime, **Codes**, **Node**, **Run**.++ Coding work is exposed to Actor **through Codes** — one coding-harness router in the estate, not two."++++## Decision++++### 1. One task contract: `platform.task@1`++++`contracts/platform/task.v1.json` (PLANNED, owner `network`) is one task at the router: the resource `POST /v1/tasks`++creates and `GET /v1/tasks/{id}` reads. It carries `id` (`tsk_<ULID>`), `project_id`, `requester` (a person or an++`agent` subject), the `task` text, the `mode`, the `budget`, the `state`, `created_at`/`finished_at`, the `result`,++the `cost`, the `explanation` (one or more `platform.placement-result@1`, oldest first, an escalation appending one),++the `progress` descriptor, the `cancel` record and the registered `webhooks`.++++- **`POST /v1/tasks` takes the task's creation subset** — `task`, `mode`, `budget`, and optionally `webhooks`. The++ router mints `id`, sets `project_id` and `requester` from the token, and answers `state: queued`.++- **It is the resource, not a device frame.** Run's `platform.job@1` is handed to a worker inside a++ `platform.job-frame@1` link message because the link is OpenVibe.Node's WebSocket. Actor's link is HTTP and++ server-sent events, so the frame vocabulary is not reused as a message type; `progress` and `explanation` carry++ what the frames carry for a job. This is the shape, and whether Actor later needs an explicit stream-event contract++ of its own (as `run.job-stream-event@1` is Run's) is left to the router's implementation.++- **The contract is `status: planned`** until T17 ships: the catalog's not-yet-implemented value (there is no++ `proposed` status), the same as the planned contracts of T14.++++### 2. The adapter interface++++An adapter is how Actor reaches one agent system, and the plan fixes what it declares: "Each adapter declares++capabilities, limits, a revisioned rate card (never AI-written) and trust class; BYO keys for any backend." So an++adapter has:++++- a **capability set** (which task classes it can take, including vision), checked before any mode or budget;++- **limits** (context, concurrency, time, egress) merged stricter with the caller's, as `platform.job@1` limits are;++- a **rate card** (`platform.rate-card@1`) with `source` and `verified_at`, revised by review and never written by an++ AI on its own;++- a **trust class** from ADR-046 §4;++- **BYO key** support, so a caller can point the adapter at their own account and pay it directly.++++The plan puts the first adapters in the engine (OpenCode, Antigravity CLI) and demands that "a new agent platform is++one adapter". The concrete interface — its field names, whether it is one manifest or a set of declared contracts,++and how it is registered — is **not fixed by the plan and is left open**; this ADR fixes only what an adapter must++declare, not the wire shape.++++### 3. Modes are planner objectives++++`mode` is one of `cheapest`, `balanced`, `best`, `fastest`, `private`; `balanced` is the default. The plan is++explicit that "a mode is a planner objective, not a hard-coded list", so a mode is stated to the placer, not++translated into a backend by hand. Four modes map onto ADR-046's `platform.placement-result@1` objectives++directly: `cheapest → cheapest`, `balanced → balanced`, `fastest → lowest-latency`, `private → private`. `best`++does **not** map to `correctness`: the SDK's `correctness` objective only preserves `req.authority` or the current++placement, so for a new task with neither it ranks like `balanced`, and `high-reliability` has no branch either —++`openvibe-sdk/placement` has no quality ranking that expresses "the highest chance of a right answer first time".++What `best` maps to is therefore left as an open question below; the placer may refine the rest, and the exact++tie-breaks inside a mode are left to `openvibe-sdk/placement`.++++### 4. Signals and auto-balancing++++Auto-balancing goes "through `openvibe-sdk/placement` over live signals per backend × task class; hysteresis keeps a++task class unless another is clearly better; failover is immediate; every decision is stored and explained." The++signals are the ones the Fabric already contracts: rate cards price a backend, `platform.telemetry-sample@1` reports++its latency, health and status, and `platform.cost-snapshot@1` measures what a class actually cost against its++alternatives. A capability the task needs is a hard filter before ranking, as `excluded()` is in the placer. The++explanation is `platform.placement-result@1`, and it is part of the task, so "which agent ran, why it won, what else++was considered and what it cost" survives the request.++++### 5. Verification and cascades++++"Cheap first, escalate on failure; cross-family checks; the task's own tests when it has them (code: tests; research:++citations; actions: postconditions)." The task therefore has a `verifying` state between `running` and `succeeded`: a++result that exists is checked before it is delivered, by a model family other than the one that produced it, and only++then becomes `result`. A failed check moves the task to a stronger backend and appends a placement result; the plan++puts the full cascade build after the router itself, so the first router may ship with a single check. A task that++never passes ends `failed`; it never returns an unchecked result as `succeeded`.++++### 6. Trust++++ADR-046 §4's five classes apply unchanged: `first-party`, `user-owned`, `partner`, `community`, `external`, with++`user-owned` eligible only when the task names it. `private` mode narrows the eligible set to backends that keep the++data on OpenVibe — `first-party`, and a `user-owned` node only when the deployment allows it — which the plan++describes as "only agents that keep your data on OpenVibe: its own runtime and open models on its hardware". Local++execution on a person's own machine goes through OpenVibe.Node under ADR-043's control levels (Observe / Ask /++Trusted / Full control), a local indicator and a kill switch the owner holds; Node's local policy always overrides a++cloud command (plan T14).++++### 7. Progress, cancel and webhooks++++- **Progress** is `progress`: the server-sent-events `stream_url`, the task's own `last_seq`, and the event kinds++ (`output`, `state`, `end`, the same three as `run.job-stream-event@1`). The stream is best-effort, as a job's stdout++ is; the task record is the source of truth.++- **Cancel** is `cancel`: `POST /v1/tasks/{id}/cancel` records who asked and when, the running backend is stopped as++ its own contract allows (a Run job through `run.job.cancel`, a Node task through its link, an external platform++ through its API), and the task ends `cancelled` with `finished_at` set. Cancel is not a promise that money already++ spent is returned.++- **Webhooks** are registered at creation: an HTTPS URL, the state changes to deliver, and an optional `secret_ref`++ naming the project secret used to sign the delivery — the name, never the secret, as++ `community.moderation-request@1` names a `webhook_url_ref` and ADR-043 keeps device credentials. A failed delivery++ is retried with backoff and never delays the task.++++### 8. Cost and budgets++++`budget.per_task_usd` and `budget.per_day_usd` are hard limits the router never exceeds; a task that cannot be run++inside `per_task_usd` fails (`actor.budget.exceeded`) instead of running, and `per_day_usd` caps the project's tasks++in one UTC day. `cost.usd` is what the task was billed, to the cent, across every attempt, and the plan's money rule++is "one key, one bill (Billing, T5), per-task cost reported to the cent; free tier from the bounded allowance". The++metering is `platform.usage-sample@1`, written through Billing as Run's job seconds are.++++## Authority boundaries++++Actor plans and routes; it does not become an authority. The plan's own list of what it calls:++++| What Actor needs | Whose authority |++|---|---|++| Coding work | Codes (the one coding-harness router) |++| Persistent conditions | Watch |++| Cloud execution | Run (`platform.job@1` / `platform.job-frame@1`) |++| Local execution and computer control | Node (ADR-043 control levels, kill switch) |++| Resource control | Services (`common.resource-control-request@1`, ADR-048) |++| Artifacts | Media |++| Robots | Bot |++| Progress and triggers | Events (ADR-042) |++| Models | AI |++++## Out of scope++++- The router, the console (T17 layer 3), persistent agents, memory, schedules and the approval inbox.++- The concrete adapter interface and its registration (decision 2 leaves it open).++- Rankings from real traffic, templates, public agents and the MCP server (later T17 steps).++- The Actor service manifest and its deploy: there is no `OpenVibe.Actor` repository yet, so the contract is owned by++ `network` until one registers a service.++++## Open questions for the owner++++1. **The adapter interface's shape.** One contract with a discriminator, or one small contract per adapter kind? The++ plan fixes what an adapter declares, not the wire shape.++2. **Per-class success as a signal.** ADR-046 contracts telemetry and cost, but not a per-backend × task-class++ success rate. Whether that is a new signal contract or an aggregation of `run.job.*`-style outcomes is open.++3. **Whether a `user-owned` node may take other people's tasks** (ADR-046's own open question), which `private` mode++ depends on.++4. **Webhook delivery guarantees** — retry count, backoff and whether an exhausted webhook surfaces on the task.++5. **Where an escalated task's partial results live** before it succeeds.++6. **What `best` maps to.** No objective in `openvibe-sdk/placement` ranks quality: `correctness` only preserves++ `req.authority` or the current placement and otherwise ranks like `balanced`, and `high-reliability` has no++ branch. Until the placer grows a real quality ranking, `best → <objective>` is undecided.++++## Consequences++++- One contract for every task, whatever backend runs it; a new backend is an adapter, not a schema change.++- Additive only: one new catalog id at 1.0.0, one id prefix (`tsk`), and a planned contract with no route and no++ capability yet, so nothing in `generated/openapi/` changes.++- The router can be built against a fixed task resource while the adapter interface stays open.++- Rollback: consumers pin the previous release tag; nothing reads `platform.task@1` until T17's router is written.++++## What this ADR does not claim++++- No Actor service, router, adapter or `POST /v1/tasks` route exists. `platform.task@1` is a contract for work not yet++ built.++- The engine's harness adapter interface is not built; this ADR decides what an adapter declares, not its fields.++- No backend is ranked, verified or metered by these rules yet; the contracts they rest on are themselves active but++ unwired for this use.@@@ -42,6 +42,7 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each| [ADR-036](ADR-036-run-execution-authority.md) | Run is an execution authority, not a Host detail: Host deploys persistent applications, Run executes isolated jobs/environments (`function`, `code`, `browser`, `linux`, `desktop`, `gpu`), placed by the Fabric on the cheapest capable offer; provider adapters are offers, not code paths; per-second metering, TTL, idle suspend, cancel, snapshot/resume, stdout streaming, artifacts via Media, limits, network policy and strong isolation | Proposed 2026-10-05 (plan T14; plan §8 lists it to write before its code, and Run's code is owner-blocked), for the owner's review || [ADR-042](ADR-042-events-fabric.md) | The events fabric: the delivery-policy class is the one vocabulary and TOPIC/QUEUE/STREAM a derived carrier class; PostgreSQL stays the record, carriers are transports that fail open to `pg-v1`; a delivery lease before any second worker; per-key ordering; the SDK planner places deliveries and explains them; JetStream for STREAM only; opaque cursors replace the global seq after one overlap release; hot/replay/archive tiers; amends ADR-004's NATS rejection | Accepted 2026-09-29 (plan T7) || [ADR-043](ADR-043-bot-devices-and-control.md) | Bot — devices, pairing, control and safety: robots, devices and credentials are separate (one hashed, rotatable, revocable device credential); pairing is a one-time code; one outbound WebSocket per device with absolute deadlines, device-side stops and a latched e-stop; robot profiles rather than rows; owner/operators/viewers with a timed public queue; everything audited; OpenVibe.Bot is its own service | Accepted 2026-09-29 (plan track T15, "Bot complete"; owner direction the same evening: openvibe.bot is an open control panel for robots, streaming optional; the first robots are the owner's Adeept 4WD Smart Car for Raspberry Pi and a Cozmo-based robot) |++| [ADR-044](ADR-044-actor-runtime.md) | The Actor runtime: one `platform.task@1` task (`POST /v1/tasks`) with a mode, a per-task and per-day budget, result, cost and explanation (a chain of `platform.placement-result@1`); adapters declare capabilities, limits, a reviewed rate card, a trust class and BYO keys; modes are `openvibe-sdk/placement` objectives; live signals auto-balance with hysteresis and immediate failover; a `verifying` state holds cheap-first cross-family checks and the task's own tests; the five ADR-046 trust classes, `private` narrowing to OpenVibe-only, Node control levels underneath; progress over SSE, cancel and webhooks; owner `network` (no Actor service manifest exists yet) | Proposed 2026-10-05 (plan T17), for the owner's review || [ADR-045](ADR-045-node-universal-runtime.md) | The Node is OpenVibe's universal runtime, not a Bot/Actor helper: one checksummed release binary and one control link serving the execution worker (`worker:<class>`, per ADR-046 §5), computer control under device-set control levels with a local kill switch and recording, and cache/storage/delivery; local policy always overrides cloud commands; ADR-043 stays the robot/device half | Proposed 2026-10-05 (plan T14 and §8 revision; consistent with ADR-046 and ADR-048, amends ADR-043's credential paragraph), for the owner's review || [ADR-046](ADR-046-universal-adaptive-fabric.md) | The universal adaptive fabric: offers, requirements, placement results and signed plans through one placer (`openvibe-sdk/placement`); health, cost, telemetry and usage samples; five trust classes, with `user-owned` eligible only when a workload names it; `worker:<class>` Node capabilities; the data plane keeps running on the last valid signed plan; T6 AI the reference consumer | Proposed 2026-10-03 (plan T1 step 3), for the owner's review || [ADR-048](ADR-048-services-control-plane.md) | The Services control plane: Services aggregates authorities and owns no other service's rows; every console action is a `common.resource-control-request@1` to the owning authority, answered with `common.resource-control-result@1` (idempotency keys, `confirmation_id` for sensitive actions); OVRN `ovrn:<service>:<project_id>:<type>/<id>` (`common.resource-name@1`) is the one cross-service resource name | Accepted 2026-10-04 (plan T13) |nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },++ "task": "Summarize the release notes.",++ "mode": "balanced",++ "budget": { "per_task_usd": 5 },++ "state": "queued",++ "created_at": "2026-10-05T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },++ "task": "Draft a launch announcement for the new pricing page.",++ "mode": "balanced",++ "budget": { "per_task_usd": 2, "per_day_usd": 20 },++ "state": "cancelled",++ "created_at": "2026-10-05T09:00:00.000Z",++ "finished_at": "2026-10-05T09:02:30.000Z",++ "result": null,++ "cost": { "usd": 0.01 }++}nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },++ "task": "Summarize the release notes.",++ "mode": "balanced",++ "budget": { "per_task_usd": 5, "per_day_usd": 50 },++ "state": "failed",++ "created_at": "2026-10-05T09:00:00.000Z",++ "finished_at": "2026-10-05T09:01:00.000Z",++ "result": { "partial": "a half-written brief" },++ "cost": { "usd": 0.02 }++}nnew file mode 100644@@@ -0,0 +1,9 @@++{++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },++ "mode": "balanced",++ "budget": { "per_task_usd": 5, "per_day_usd": 50 },++ "state": "queued",++ "created_at": "2026-10-05T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },++ "task": "Summarize the release notes.",++ "mode": "balanced",++ "budget": { "per_task_usd": -1, "per_day_usd": 50 },++ "state": "queued",++ "created_at": "2026-10-05T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },++ "task": "Summarize the release notes.",++ "mode": "balanced",++ "budget": { "per_task_usd": 5, "per_day_usd": 50 },++ "state": "queued",++ "created_at": "2026-10-05T09:00:00.000Z",++ "webhooks": [++ { "url": "http://example.com/hooks/actor", "events": ["succeeded"] }++ ]++}nnew file mode 100644@@@ -0,0 +1,20 @@++{++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": { "type": "agent", "id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPT" },++ "task": "Check this week's outage report and summarize the causes.",++ "mode": "cheapest",++ "budget": { "per_task_usd": 1, "per_day_usd": 10 },++ "state": "succeeded",++ "created_at": "2026-10-05T09:00:00.000Z",++ "finished_at": "2026-10-05T09:06:00.000Z",++ "cost": { "usd": 0.06 },++ "explanation": [++ {++ "selected": "open-model",++ "objective": "cheapest",++ "candidates": [{ "id": "open-model", "eligible": true }],++ "decided_at": "2026-10-05T09:00:01.000Z"++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },++ "task": "Summarize the release notes.",++ "mode": "smart",++ "budget": { "per_task_usd": 5, "per_day_usd": 50 },++ "state": "queued",++ "created_at": "2026-10-05T09:00:00.000Z"++}nnew file mode 100644@@@ -0,0 +1,27 @@++{++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },++ "task": "Draft a launch announcement for the new pricing page.",++ "mode": "balanced",++ "budget": { "per_task_usd": 2, "per_day_usd": 20 },++ "state": "cancelled",++ "created_at": "2026-10-05T09:00:00.000Z",++ "finished_at": "2026-10-05T09:02:30.000Z",++ "result": null,++ "cost": { "usd": 0.01 },++ "explanation": [++ {++ "selected": "hosted-agent",++ "objective": "balanced",++ "reasons": ["writing task; first attempt still running when the caller cancelled"],++ "candidates": [{ "id": "hosted-agent", "eligible": true, "estimated_cost_usd": 0.31 }],++ "decided_at": "2026-10-05T09:00:01.000Z"++ }++ ],++ "cancel": {++ "requested_at": "2026-10-05T09:02:29.000Z",++ "requested_by": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" }++ },++ "progress": null++}nnew file mode 100644@@@ -0,0 +1,13 @@++{++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },++ "task": "Research five competitors of my shop and write a one-page brief with sources.",++ "mode": "balanced",++ "budget": { "per_task_usd": 5, "per_day_usd": 50 },++ "state": "queued",++ "created_at": "2026-10-05T09:00:00.000Z",++ "webhooks": [++ { "url": "https://example.com/hooks/actor", "events": ["succeeded", "failed"], "secret_ref": "ACTOR_WEBHOOK_SECRET" }++ ]++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },++ "task": "Fix the failing thumbnail test in OpenVibe.Media and open a PR.",++ "mode": "best",++ "budget": { "per_task_usd": 5, "per_day_usd": 50 },++ "state": "running",++ "created_at": "2026-10-05T09:00:00.000Z",++ "progress": {++ "stream_url": "https://openvibe.actor/v1/tasks/tsk_01JAB2C3D4E5F6G7H8J9K0MNPR/events",++ "last_seq": 12,++ "events": ["output", "state", "end"]++ },++ "explanation": [++ {++ "selected": "openvibe-runtime",++ "objective": "correctness",++ "reasons": ["the task is code; the Codes adapter exposes a code harness"],++ "candidates": [{ "id": "openvibe-runtime", "eligible": true, "estimated_cost_usd": 0.06 }],++ "decided_at": "2026-10-05T09:00:01.000Z"++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": { "type": "agent", "id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPT" },++ "task": "Check this week's outage report and summarize the causes.",++ "mode": "cheapest",++ "budget": { "per_task_usd": 1, "per_day_usd": 10 },++ "state": "succeeded",++ "created_at": "2026-10-05T09:00:00.000Z",++ "finished_at": "2026-10-05T09:06:00.000Z",++ "result": { "brief": "Two outages; both were a saturated connection pool.", "citations": ["https://openvibe.status/incidents/41"] },++ "cost": { "usd": 0.06, "free_allowance_used": 0.06 },++ "explanation": [++ {++ "selected": "open-model",++ "objective": "cheapest",++ "reasons": ["research task; the checks are citations, met by the cheapest candidate"],++ "candidates": [{ "id": "open-model", "eligible": true, "estimated_cost_usd": 0.06 }],++ "decided_at": "2026-10-05T09:00:01.000Z"++ }++ ],++ "cancel": null,++ "progress": null++}@@@ -64599,6 +64599,439 @@]}},++ "platform.task": {++ "version": "1.0.0",++ "owner": "network",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/platform/task.v1.json",++ "title": "Task",++ "description": "platform.task@1 (PLANNED; plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /v1/tasks creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered adapters, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /v1/tasks/{id} reads the same resource. Progress streams as server-sent events from `progress.stream_url` (a task's own counter in `progress.last_seq`, as platform.job-frame@1 counts on a link); `cancel` records that a cancel was requested (POST /v1/tasks/{id}/cancel), and `webhooks` are the registered endpoints the router delivers each state change to. No Actor service exists yet: OpenVibe.Actor is a product page (manifests/products/openvibe.actor.json, noRepo) with no service manifest, so the contract is owned by `network`, like every other platform.* contract, and stays PLANNED until T17 ships. This v1 contract may grow optional fields.",++ "$comment": "Additive optional fields may extend this v1 contract. It is the task resource, not a device frame: Actor's link is HTTP/SSE where Run's is the Node WebSocket.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "project_id",++ "requester",++ "task",++ "mode",++ "budget",++ "state",++ "created_at"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^tsk_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "Minted by the router (tsk_<ULID>): the idempotency key of the task and the stem of its usage readings, as platform.job@1's id is for a job. Never reused."++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's."++ },++ "requester": {++ "description": "The person or agent the task belongs to: a user subject for a person, an agent subject for an agent acting under the grants its owner delegated (roadmap WS-Z2).",++ "allOf": [++ {++ "$ref": "../identity/subject-ref.v1.json"++ }++ ]++ },++ "task": {++ "type": "string",++ "minLength": 1,++ "maxLength": 100000,++ "description": "The instruction in plain words, exactly as the caller gave it. Not a backend command and not a model prompt: the planner turns it into one."++ },++ "mode": {++ "type": "string",++ "enum": [++ "cheapest",++ "balanced",++ "best",++ "fastest",++ "private"++ ],++ "default": "balanced",++ "description": "The plan's five modes, in contract spelling: Cheapest, Balanced (the default), Best, Fastest and Private are `cheapest`, `balanced`, `best`, `fastest` and `private`. A mode is the planner objective, never a fixed backend list (plan T17). `cheapest`: the lowest expected cost that still meets the task class's quality bar. `balanced` (the default): cost, quality and speed weighed together. `best`: the highest chance of a right answer first time. `fastest`: the shortest time to a checked result. `private`: only backends that keep the data on OpenVibe (first-party, and a user-owned node only when the task names it), per ADR-046."++ },++ "budget": {++ "type": "object",++ "additionalProperties": false,++ "description": "Hard limits the router never exceeds (plan T17: 'a hard limit per task and per day'). A task whose cheapest eligible backend would take it over `per_task_usd` ends failed (actor.budget.exceeded) rather than running; `per_day_usd` caps the project's tasks in one UTC day and a new task waits or fails, it never overruns.",++ "required": [++ "per_task_usd",++ "per_day_usd"++ ],++ "properties": {++ "per_task_usd": {++ "type": "number",++ "minimum": 0++ },++ "per_day_usd": {++ "type": "number",++ "minimum": 0++ }++ }++ },++ "state": {++ "type": "string",++ "enum": [++ "queued",++ "running",++ "verifying",++ "succeeded",++ "failed",++ "cancelled"++ ],++ "description": "queued: stored, waiting for the planner. running: a backend is working, or a cascade is between attempts. verifying: a result exists and a check (the task's own tests, a citation check, a postcondition) runs before it is delivered. succeeded, failed and cancelled are the end states; an end state never changes."++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "finished_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "When the task reached its end state; null before."++ },++ "result": {++ "description": "The checked deliverable (any JSON, as a platform.job@1 `result` is): for a code task a diff or branch reference, for research a brief with citations, for an action a postcondition report. Never a credential and never raw sandbox output. Null (or absent) in every state but succeeded, which the allOf below enforces."++ },++ "cost": {++ "anyOf": [++ {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "usd"++ ],++ "properties": {++ "usd": {++ "type": "number",++ "minimum": 0,++ "description": "What this task was billed, to the cent, across every attempt it made."++ },++ "free_allowance_used": {++ "type": "number",++ "minimum": 0,++ "description": "The part of `usd` covered by the project's free allowance; metered as platform.usage-sample@1."++ }++ }++ },++ {++ "type": "null"++ }++ ],++ "description": "Present once the task has run an attempt (also when it ends failed or cancelled); null while queued. One key, one bill (plan T17), through Billing (ADR-012)."++ },++ "explanation": {++ "anyOf": [++ {++ "type": "array",++ "minItems": 1,++ "items": {++ "$ref": "placement-result.v1.json"++ }++ },++ {++ "type": "null"++ }++ ],++ "description": "One platform.placement-result@1 per backend the planner tried, oldest first, an escalation appending a new one: which backend won, why and what else was considered, from openvibe-sdk/placement (ADR-046 §1). Null while queued."++ },++ "progress": {++ "anyOf": [++ {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "stream_url",++ "last_seq"++ ],++ "properties": {++ "stream_url": {++ "type": "string",++ "format": "uri",++ "description": "The text/event-stream endpoint that carries this task's progress. Each event mirrors platform.job-frame@1's job_stdout/state shape: a state change, an output chunk, or the end."++ },++ "last_seq": {++ "type": "integer",++ "minimum": 0,++ "description": "The task's own event counter, from 0 before the first event; a reconnecting reader resumes after it, as a job frame's seq counts on one link."++ },++ "events": {++ "type": "array",++ "items": {++ "type": "string",++ "enum": [++ "output",++ "state",++ "end"++ ]++ },++ "description": "The event kinds the stream delivers, run.job-stream-event@1's own three."++ }++ }++ },++ {++ "type": "null"++ }++ ],++ "description": "Streaming progress (plan T17). Null before the router opens a stream and after the end."++ },++ "cancel": {++ "anyOf": [++ {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "requested_at"++ ],++ "properties": {++ "requested_at": {++ "type": "string",++ "format": "date-time",++ "description": "When POST /v1/tasks/{id}/cancel was accepted. The running backend is stopped as its own contract allows; the state becomes cancelled and `finished_at` is set."++ },++ "requested_by": {++ "$ref": "../identity/subject-ref.v1.json"++ }++ }++ },++ {++ "type": "null"++ }++ ],++ "description": "Cancellation (plan T17): null until a cancel is requested, then it records who asked and when; the task's outcome is `state`."++ },++ "webhooks": {++ "type": "array",++ "maxItems": 8,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "url",++ "events"++ ],++ "properties": {++ "url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://",++ "description": "The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused."++ },++ "events": {++ "type": "array",++ "minItems": 1,++ "items": {++ "type": "string",++ "enum": [++ "queued",++ "running",++ "verifying",++ "succeeded",++ "failed",++ "cancelled"++ ]++ },++ "description": "The state changes delivered; the same names as `state`."++ },++ "secret_ref": {++ "type": "string",++ "pattern": "^[A-Z0-9_]+$",++ "description": "The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference."++ }++ }++ },++ "description": "Webhooks (plan T17), registered at creation. A delivery that fails is retried with backoff and never delays the task; the task's own record is the source of truth."++ }++ },++ "allOf": [++ {++ "if": {++ "required": [++ "state"++ ],++ "properties": {++ "state": {++ "const": "succeeded"++ }++ }++ },++ "then": {++ "required": [++ "result",++ "cost",++ "explanation"++ ],++ "properties": {++ "result": {++ "not": {++ "type": "null"++ }++ },++ "cost": {++ "type": "object"++ },++ "explanation": {++ "type": "array"++ }++ }++ },++ "else": {++ "properties": {++ "result": {++ "type": "null"++ }++ }++ }++ },++ {++ "if": {++ "required": [++ "state"++ ],++ "properties": {++ "state": {++ "const": "cancelled"++ }++ }++ },++ "then": {++ "required": [++ "cancel"++ ]++ }++ }++ ],++ "examples": [++ {++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"++ },++ "task": "Research five competitors of my shop and write a one-page brief with sources.",++ "mode": "balanced",++ "budget": {++ "per_task_usd": 5,++ "per_day_usd": 50++ },++ "state": "queued",++ "created_at": "2026-10-05T09:00:00.000Z",++ "webhooks": [++ {++ "url": "https://example.com/hooks/actor",++ "events": [++ "succeeded",++ "failed"++ ],++ "secret_ref": "ACTOR_WEBHOOK_SECRET"++ }++ ]++ },++ {++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"++ },++ "task": "Fix the failing thumbnail test in OpenVibe.Media and open a PR.",++ "mode": "best",++ "budget": {++ "per_task_usd": 5,++ "per_day_usd": 50++ },++ "state": "running",++ "created_at": "2026-10-05T09:00:00.000Z",++ "progress": {++ "stream_url": "https://openvibe.actor/v1/tasks/tsk_01JAB2C3D4E5F6G7H8J9K0MNPR/events",++ "last_seq": 12,++ "events": [++ "output",++ "state",++ "end"++ ]++ },++ "explanation": [++ {++ "selected": "openvibe-runtime",++ "objective": "correctness",++ "reasons": [++ "the task is code; the Codes adapter exposes a code harness"++ ],++ "candidates": [++ {++ "id": "openvibe-runtime",++ "eligible": true,++ "estimated_cost_usd": 0.06,++ "estimated_latency_ms": 180000++ }++ ],++ "decided_at": "2026-10-05T09:00:01.000Z"++ }++ ]++ },++ {++ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "agent",++ "id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPT"++ },++ "task": "Check this week's outage report and summarize the causes.",++ "mode": "cheapest",++ "budget": {++ "per_task_usd": 1,++ "per_day_usd": 10++ },++ "state": "succeeded",++ "created_at": "2026-10-05T09:00:00.000Z",++ "finished_at": "2026-10-05T09:06:00.000Z",++ "result": {++ "brief": "Two outages; both were a saturated connection pool.",++ "citations": [++ "https://openvibe.status/incidents/41"++ ]++ },++ "cost": {++ "usd": 0.06,++ "free_allowance_used": 0.06++ },++ "explanation": [++ {++ "selected": "open-model",++ "objective": "cheapest",++ "reasons": [++ "research task; the checks are citations, met by the cheapest candidate"++ ],++ "candidates": [++ {++ "id": "open-model",++ "eligible": true,++ "estimated_cost_usd": 0.06,++ "estimated_latency_ms": 420000++ }++ ],++ "decided_at": "2026-10-05T09:00:01.000Z"++ }++ ],++ "cancel": null,++ "progress": null++ }++ ]++ }++ },"events.delivery-policy": {"version": "1.0.0","owner": "events",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.AI API",-- "version": "0.100.0",-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.Network API",-- "version": "0.100.0",-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.101.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": "OpenRe.Stream API",-- "version": "0.100.0",-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",++ "version": "0.101.0",++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.101.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"@@@ -52604,6 +52604,808 @@ export type JobFrame =};};++/** [email protected] (owner: network) */++/**++ * platform.task@1 (PLANNED; plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /v1/tasks creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered adapters, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /v1/tasks/{id} reads the same resource. Progress streams as server-sent events from `progress.stream_url` (a task's own counter in `progress.last_seq`, as platform.job-frame@1 counts on a link); `cancel` records that a cancel was requested (POST /v1/tasks/{id}/cancel), and `webhooks` are the registered endpoints the router delivers each state change to. No Actor service exists yet: OpenVibe.Actor is a product page (manifests/products/openvibe.actor.json, noRepo) with no service manifest, so the contract is owned by `network`, like every other platform.* contract, and stays PLANNED until T17 ships. This v1 contract may grow optional fields.++ */++export type Task = {++ [k: string]: unknown | undefined;++} & {++ /**++ * Minted by the router (tsk_<ULID>): the idempotency key of the task and the stem of its usage readings, as platform.job@1's id is for a job. Never reused.++ */++ id: string;++ /**++ * The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's.++ */++ project_id: string;++ /**++ * The person or agent the task belongs to: a user subject for a person, an agent subject for an agent acting under the grants its owner delegated (roadmap WS-Z2).++ */++ requester: SubjectRef;++ /**++ * The instruction in plain words, exactly as the caller gave it. Not a backend command and not a model prompt: the planner turns it into one.++ */++ task: string;++ /**++ * The plan's five modes, in contract spelling: Cheapest, Balanced (the default), Best, Fastest and Private are `cheapest`, `balanced`, `best`, `fastest` and `private`. A mode is the planner objective, never a fixed backend list (plan T17). `cheapest`: the lowest expected cost that still meets the task class's quality bar. `balanced` (the default): cost, quality and speed weighed together. `best`: the highest chance of a right answer first time. `fastest`: the shortest time to a checked result. `private`: only backends that keep the data on OpenVibe (first-party, and a user-owned node only when the task names it), per ADR-046.++ */++ mode: "cheapest" | "balanced" | "best" | "fastest" | "private";++ /**++ * Hard limits the router never exceeds (plan T17: 'a hard limit per task and per day'). A task whose cheapest eligible backend would take it over `per_task_usd` ends failed (actor.budget.exceeded) rather than running; `per_day_usd` caps the project's tasks in one UTC day and a new task waits or fails, it never overruns.++ */++ budget: {++ per_task_usd: number;++ per_day_usd: number;++ };++ /**++ * queued: stored, waiting for the planner. running: a backend is working, or a cascade is between attempts. verifying: a result exists and a check (the task's own tests, a citation check, a postcondition) runs before it is delivered. succeeded, failed and cancelled are the end states; an end state never changes.++ */++ state: "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled";++ created_at: string;++ /**++ * When the task reached its end state; null before.++ */++ finished_at?: string | null;++ /**++ * The checked deliverable (any JSON, as a platform.job@1 `result` is): for a code task a diff or branch reference, for research a brief with citations, for an action a postcondition report. Never a credential and never raw sandbox output. Null (or absent) in every state but succeeded, which the allOf below enforces.++ */++ result?: {++ [k: string]: unknown | undefined;++ };++ /**++ * Present once the task has run an attempt (also when it ends failed or cancelled); null while queued. One key, one bill (plan T17), through Billing (ADR-012).++ */++ cost?: {++ /**++ * What this task was billed, to the cent, across every attempt it made.++ */++ usd: number;++ /**++ * The part of `usd` covered by the project's free allowance; metered as platform.usage-sample@1.++ */++ free_allowance_used?: number;++ } | null;++ /**++ * One platform.placement-result@1 per backend the planner tried, oldest first, an escalation appending a new one: which backend won, why and what else was considered, from openvibe-sdk/placement (ADR-046 §1). Null while queued.++ */++ explanation?: [PlacementResult, ...PlacementResult[]] | null;++ /**++ * Streaming progress (plan T17). Null before the router opens a stream and after the end.++ */++ progress?: {++ /**++ * The text/event-stream endpoint that carries this task's progress. Each event mirrors platform.job-frame@1's job_stdout/state shape: a state change, an output chunk, or the end.++ */++ stream_url: string;++ /**++ * The task's own event counter, from 0 before the first event; a reconnecting reader resumes after it, as a job frame's seq counts on one link.++ */++ last_seq: number;++ /**++ * The event kinds the stream delivers, run.job-stream-event@1's own three.++ */++ events?: ("output" | "state" | "end")[];++ } | null;++ /**++ * Cancellation (plan T17): null until a cancel is requested, then it records who asked and when; the task's outcome is `state`.++ */++ cancel?: {++ /**++ * When POST /v1/tasks/{id}/cancel was accepted. The running backend is stopped as its own contract allows; the state becomes cancelled and `finished_at` is set.++ */++ requested_at: string;++ requested_by?: SubjectRef;++ } | null;++ /**++ * Webhooks (plan T17), registered at creation. A delivery that fails is retried with backoff and never delays the task; the task's own record is the source of truth.++ *++ * @maxItems 8++ */++ webhooks?:++ | []++ | [++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ }++ ]++ | [++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ }++ ]++ | [++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ }++ ]++ | [++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ }++ ]++ | [++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ }++ ]++ | [++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ }++ ]++ | [++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ }++ ]++ | [++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ },++ {++ /**++ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.++ */++ url: string;++ /**++ * The state changes delivered; the same names as `state`.++ *++ * @minItems 1++ */++ events: [++ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",++ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]++ ];++ /**++ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.++ */++ secret_ref?: string;++ }++ ];++};++/** [email protected] (owner: events) *//*** How an event type is carried (roadmap WS-Z3 task 2): its delivery class sets the minimum semantics, which a publisher's intent may raise and never lower; ordering is per key; the route planner picks the carrier.@@@ -6,7 +6,7 @@const crypto = require('crypto');const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';--const PREFIX = { user: 'usr', guest: 'gst', app: 'app', mod: 'mod', agent: 'agt', node: 'nod', event: 'evt', media: 'med', project: 'prj', confirmation: 'cnf', job: 'job', watch: 'wch', observation: 'wco', check: 'ckr' };++const PREFIX = { user: 'usr', guest: 'gst', app: 'app', mod: 'mod', agent: 'agt', node: 'nod', event: 'evt', media: 'med', project: 'prj', confirmation: 'cnf', job: 'job', task: 'tsk', watch: 'wch', observation: 'wco', check: 'ckr' };const SLUG_TYPES = new Set(['service', 'system']);const ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;const SLUG_RE = /^[a-z][a-z0-9-]{1,39}$/;@@@ -1,12 +1,12 @@{"name": "openvibe-contracts",-- "version": "0.100.0",++ "version": "0.101.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-contracts",-- "version": "0.100.0",++ "version": "0.101.0","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.100.0",++ "version": "0.101.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",@@@ -531,7 +531,7 @@ ok(nodeId.startsWith('nod_') && ids.principalSub({ type: 'node', id: nodeId }) =assert.throws(() => ids.principalSub({ type: 'node', id: 'nod_42' }), /principals/);ok(!ids.SUBJECT_TYPES.includes('node') && ids.parseSubject(`node:${nodeId}`) === null, 'a node is a principal, never a subject');assert.throws(() => ids.principalSub({ type: 'user', id: ids.newId('user') }), /principals/);--for (const [kind, prefix] of [['watch', 'wch'], ['observation', 'wco'], ['check', 'ckr'], ['job', 'job']]) ok(ids.newId(kind).startsWith(`${prefix}_`), `newId(${kind}) is ${prefix}_<ULID>`);++for (const [kind, prefix] of [['watch', 'wch'], ['observation', 'wco'], ['check', 'ckr'], ['job', 'job'], ['task', 'tsk']]) ok(ids.newId(kind).startsWith(`${prefix}_`), `newId(${kind}) is ${prefix}_<ULID>`);ok(contracts.validate('media.media-ref', { media_id: ids.legacyMediaId('live', 'vod', 42) }).valid, 'legacyMediaId is a valid MediaRef');ok(contracts.validate('events.event-envelope', { event_id: ids.newId('event'), event_type: 'network.user.created', version: 1, source: 'network', actor: { type: 'system', id: 'network' }, timestamp: new Date().toISOString(), subject: { type: 'user', id: ids.newId('user') }, payload: {} }).valid, 'newId(event) builds a valid envelope');
@@ -4,6 +4,25 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -4,6 +4,25 @@ 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.101.0 — 2026-10-05
+
+**The task contract** (plan T17, ADR-044). Additive: one new planned contract and its ADR, no existing schema changed.
+- `platform.task@1` (new, owner `network`, public, **planned**): one task at OpenVibe.Actor's router — the resource
+ `POST /v1/tasks` creates and `GET /v1/tasks/{id}` reads, modelled on Run's `platform.job@1`. It carries `id`
+ (`tsk_<ULID>`), `project_id`, `requester` (a person or an `agent` subject), the `task` text, `mode`
+ (`cheapest | balanced | best | fastest | private`), `budget` (`per_task_usd`, `per_day_usd`), `state`
+ (`queued | running | verifying | succeeded | failed | cancelled`), `created_at`/`finished_at`, `result`, `cost`,
+ `explanation` (a chain of `platform.placement-result@1`, an escalation appending one), the SSE `progress`
+ descriptor, the `cancel` record and registered `webhooks` (HTTPS only, secret by reference). Valid and invalid
+ fixtures in `fixtures/platform.task/`.
+- `docs/adr/ADR-044-actor-runtime.md` (new, **Proposed**; plan T17, the build-order item after the engine): the task
+ contract, the adapter interface (capabilities, limits, a reviewed rate card, trust class and BYO keys), modes as
+ `openvibe-sdk/placement` objectives, live signals with hysteresis and immediate failover, a `verifying` state for
+ cheap-first cross-family checks and the task's own tests, and the five ADR-046 trust classes with `private`
+ narrowing to OpenVibe-only. Owner is `network` because no Actor service manifest exists: OpenVibe.Actor is a product
+ page (`manifests/products/openvibe.actor.json`, `noRepo`), and every other `platform.*` contract is Network's too.
+- `lib/ids.js`: the `task` prefix (`tsk`), so `ids.newId('task')` mints the contract's id.
+
## 0.100.0 — 2026-10-05
## 0.100.0 — 2026-10-05
**Three small contract fixes** (plan follow-ups). Additive and widening, no contract removed.
**Three small contract fixes** (plan follow-ups). Additive and widening, no contract removed.
@@ -7,7 +7,7 @@
@@ -7,7 +7,7 @@
"plan": "OpenVibe Development Roadmap (combined), Wave 1; implementation plan rev 3 section 3.1",
"plan": "OpenVibe Development Roadmap (combined), Wave 1; implementation plan rev 3 section 3.1",
"deployed": false,
"deployed": false,
"deployment": "none of its own: a library and artifact repository published as release tags; services pin a tag and deploy it with their own releases",
"deployment": "none of its own: a library and artifact repository published as release tags; services pin a tag and deploy it with their own releases",
- "contracts": "openvibe-contracts v0.100.0 (this package; the newest tag)",
+ "contracts": "openvibe-contracts v0.101.0 (this package; the newest tag)",
"features": [
"features": [
"589 JSON Schemas with valid and invalid fixtures",
"589 JSON Schemas with valid and invalid fixtures",
"31 service manifests with lifecycle declarations",
"31 service manifests with lifecycle declarations",
@@ -19,7 +19,8 @@
@@ -19,7 +19,8 @@
"compatibility check against the previous tag (scripts/compat.js)",
"compatibility check against the previous tag (scripts/compat.js)",
"documentation-currency check (scripts/docs-currency.js)",
"documentation-currency check (scripts/docs-currency.js)",
"generated estate: docs/ESTATE.md and manifests/repositories/ from the checkouts (scripts/estate.js; --check fails on drift)",
"generated estate: docs/ESTATE.md and manifests/repositories/ from the checkouts (scripts/estate.js; --check fails on drift)",
- "chat.ticket-conversation@1 and the planned chat.ticket.write capability: a per-ticket conversation keyed by ticket id and calling service, with author_kind person|agent|staff"
+ "chat.ticket-conversation@1 and the planned chat.ticket.write capability: a per-ticket conversation keyed by ticket id and calling service, with author_kind person|agent|staff",
+ "platform.task@1 (planned, owner network) and ADR-044: one task at OpenVibe.Actor's router with a mode, a per-task and per-day budget, result, cost and explanation, SSE progress, cancel and webhooks"
],
],
"updated": "2026-10-05"
"updated": "2026-10-05"
}
}
@@ -5619,6 +5619,16 @@
@@ -5619,6 +5619,16 @@
"status": "active",
"status": "active",
"adr": "ADR-034"
"adr": "ADR-034"
},
},
+ {
+ "id": "platform.task",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "platform/task.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-044"
+ },
{
{
"id": "events.delivery-policy",
"id": "events.delivery-policy",
"version": "1.0.0",
"version": "1.0.0",
new file mode 100644
new file mode 100644
@@ -0,0 +1,428 @@
@@ -0,0 +1,428 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/platform/task.v1.json",
+ "title": "Task",
+ "description": "platform.task@1 (PLANNED; plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /v1/tasks creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered adapters, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /v1/tasks/{id} reads the same resource. Progress streams as server-sent events from `progress.stream_url` (a task's own counter in `progress.last_seq`, as platform.job-frame@1 counts on a link); `cancel` records that a cancel was requested (POST /v1/tasks/{id}/cancel), and `webhooks` are the registered endpoints the router delivers each state change to. No Actor service exists yet: OpenVibe.Actor is a product page (manifests/products/openvibe.actor.json, noRepo) with no service manifest, so the contract is owned by `network`, like every other platform.* contract, and stays PLANNED until T17 ships. This v1 contract may grow optional fields.",
+ "$comment": "Additive optional fields may extend this v1 contract. It is the task resource, not a device frame: Actor's link is HTTP/SSE where Run's is the Node WebSocket.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "project_id",
+ "requester",
+ "task",
+ "mode",
+ "budget",
+ "state",
+ "created_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^tsk_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "Minted by the router (tsk_<ULID>): the idempotency key of the task and the stem of its usage readings, as platform.job@1's id is for a job. Never reused."
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's."
+ },
+ "requester": {
+ "description": "The person or agent the task belongs to: a user subject for a person, an agent subject for an agent acting under the grants its owner delegated (roadmap WS-Z2).",
+ "allOf": [
+ {
+ "$ref": "../identity/subject-ref.v1.json"
+ }
+ ]
+ },
+ "task": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100000,
+ "description": "The instruction in plain words, exactly as the caller gave it. Not a backend command and not a model prompt: the planner turns it into one."
+ },
+ "mode": {
+ "type": "string",
+ "enum": [
+ "cheapest",
+ "balanced",
+ "best",
+ "fastest",
+ "private"
+ ],
+ "default": "balanced",
+ "description": "The plan's five modes, in contract spelling: Cheapest, Balanced (the default), Best, Fastest and Private are `cheapest`, `balanced`, `best`, `fastest` and `private`. A mode is the planner objective, never a fixed backend list (plan T17). `cheapest`: the lowest expected cost that still meets the task class's quality bar. `balanced` (the default): cost, quality and speed weighed together. `best`: the highest chance of a right answer first time. `fastest`: the shortest time to a checked result. `private`: only backends that keep the data on OpenVibe (first-party, and a user-owned node only when the task names it), per ADR-046."
+ },
+ "budget": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "Hard limits the router never exceeds (plan T17: 'a hard limit per task and per day'). A task whose cheapest eligible backend would take it over `per_task_usd` ends failed (actor.budget.exceeded) rather than running; `per_day_usd` caps the project's tasks in one UTC day and a new task waits or fails, it never overruns.",
+ "required": [
+ "per_task_usd",
+ "per_day_usd"
+ ],
+ "properties": {
+ "per_task_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "per_day_usd": {
+ "type": "number",
+ "minimum": 0
+ }
+ }
+ },
+ "state": {
+ "type": "string",
+ "enum": [
+ "queued",
+ "running",
+ "verifying",
+ "succeeded",
+ "failed",
+ "cancelled"
+ ],
+ "description": "queued: stored, waiting for the planner. running: a backend is working, or a cascade is between attempts. verifying: a result exists and a check (the task's own tests, a citation check, a postcondition) runs before it is delivered. succeeded, failed and cancelled are the end states; an end state never changes."
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "When the task reached its end state; null before."
+ },
+ "result": {
+ "description": "The checked deliverable (any JSON, as a platform.job@1 `result` is): for a code task a diff or branch reference, for research a brief with citations, for an action a postcondition report. Never a credential and never raw sandbox output. Null (or absent) in every state but succeeded, which the allOf below enforces."
+ },
+ "cost": {
+ "anyOf": [
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "usd"
+ ],
+ "properties": {
+ "usd": {
+ "type": "number",
+ "minimum": 0,
+ "description": "What this task was billed, to the cent, across every attempt it made."
+ },
+ "free_allowance_used": {
+ "type": "number",
+ "minimum": 0,
+ "description": "The part of `usd` covered by the project's free allowance; metered as platform.usage-sample@1."
+ }
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Present once the task has run an attempt (also when it ends failed or cancelled); null while queued. One key, one bill (plan T17), through Billing (ADR-012)."
+ },
+ "explanation": {
+ "anyOf": [
+ {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "placement-result.v1.json"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "One platform.placement-result@1 per backend the planner tried, oldest first, an escalation appending a new one: which backend won, why and what else was considered, from openvibe-sdk/placement (ADR-046 §1). Null while queued."
+ },
+ "progress": {
+ "anyOf": [
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "stream_url",
+ "last_seq"
+ ],
+ "properties": {
+ "stream_url": {
+ "type": "string",
+ "format": "uri",
+ "description": "The text/event-stream endpoint that carries this task's progress. Each event mirrors platform.job-frame@1's job_stdout/state shape: a state change, an output chunk, or the end."
+ },
+ "last_seq": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "The task's own event counter, from 0 before the first event; a reconnecting reader resumes after it, as a job frame's seq counts on one link."
+ },
+ "events": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "output",
+ "state",
+ "end"
+ ]
+ },
+ "description": "The event kinds the stream delivers, run.job-stream-event@1's own three."
+ }
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Streaming progress (plan T17). Null before the router opens a stream and after the end."
+ },
+ "cancel": {
+ "anyOf": [
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "requested_at"
+ ],
+ "properties": {
+ "requested_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When POST /v1/tasks/{id}/cancel was accepted. The running backend is stopped as its own contract allows; the state becomes cancelled and `finished_at` is set."
+ },
+ "requested_by": {
+ "$ref": "../identity/subject-ref.v1.json"
+ }
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Cancellation (plan T17): null until a cancel is requested, then it records who asked and when; the task's outcome is `state`."
+ },
+ "webhooks": {
+ "type": "array",
+ "maxItems": 8,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "url",
+ "events"
+ ],
+ "properties": {
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://",
+ "description": "The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused."
+ },
+ "events": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "enum": [
+ "queued",
+ "running",
+ "verifying",
+ "succeeded",
+ "failed",
+ "cancelled"
+ ]
+ },
+ "description": "The state changes delivered; the same names as `state`."
+ },
+ "secret_ref": {
+ "type": "string",
+ "pattern": "^[A-Z0-9_]+$",
+ "description": "The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference."
+ }
+ }
+ },
+ "description": "Webhooks (plan T17), registered at creation. A delivery that fails is retried with backoff and never delays the task; the task's own record is the source of truth."
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "required": [
+ "state"
+ ],
+ "properties": {
+ "state": {
+ "const": "succeeded"
+ }
+ }
+ },
+ "then": {
+ "required": [
+ "result",
+ "cost",
+ "explanation"
+ ],
+ "properties": {
+ "result": {
+ "not": {
+ "type": "null"
+ }
+ },
+ "cost": {
+ "type": "object"
+ },
+ "explanation": {
+ "type": "array"
+ }
+ }
+ },
+ "else": {
+ "properties": {
+ "result": {
+ "type": "null"
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "required": [
+ "state"
+ ],
+ "properties": {
+ "state": {
+ "const": "cancelled"
+ }
+ }
+ },
+ "then": {
+ "required": [
+ "cancel"
+ ]
+ }
+ }
+ ],
+ "examples": [
+ {
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
+ },
+ "task": "Research five competitors of my shop and write a one-page brief with sources.",
+ "mode": "balanced",
+ "budget": {
+ "per_task_usd": 5,
+ "per_day_usd": 50
+ },
+ "state": "queued",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "webhooks": [
+ {
+ "url": "https://example.com/hooks/actor",
+ "events": [
+ "succeeded",
+ "failed"
+ ],
+ "secret_ref": "ACTOR_WEBHOOK_SECRET"
+ }
+ ]
+ },
+ {
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
+ },
+ "task": "Fix the failing thumbnail test in OpenVibe.Media and open a PR.",
+ "mode": "best",
+ "budget": {
+ "per_task_usd": 5,
+ "per_day_usd": 50
+ },
+ "state": "running",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "progress": {
+ "stream_url": "https://openvibe.actor/v1/tasks/tsk_01JAB2C3D4E5F6G7H8J9K0MNPR/events",
+ "last_seq": 12,
+ "events": [
+ "output",
+ "state",
+ "end"
+ ]
+ },
+ "explanation": [
+ {
+ "selected": "openvibe-runtime",
+ "objective": "correctness",
+ "reasons": [
+ "the task is code; the Codes adapter exposes a code harness"
+ ],
+ "candidates": [
+ {
+ "id": "openvibe-runtime",
+ "eligible": true,
+ "estimated_cost_usd": 0.06,
+ "estimated_latency_ms": 180000
+ }
+ ],
+ "decided_at": "2026-10-05T09:00:01.000Z"
+ }
+ ]
+ },
+ {
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "agent",
+ "id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPT"
+ },
+ "task": "Check this week's outage report and summarize the causes.",
+ "mode": "cheapest",
+ "budget": {
+ "per_task_usd": 1,
+ "per_day_usd": 10
+ },
+ "state": "succeeded",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "finished_at": "2026-10-05T09:06:00.000Z",
+ "result": {
+ "brief": "Two outages; both were a saturated connection pool.",
+ "citations": [
+ "https://openvibe.status/incidents/41"
+ ]
+ },
+ "cost": {
+ "usd": 0.06,
+ "free_allowance_used": 0.06
+ },
+ "explanation": [
+ {
+ "selected": "open-model",
+ "objective": "cheapest",
+ "reasons": [
+ "research task; the checks are citations, met by the cheapest candidate"
+ ],
+ "candidates": [
+ {
+ "id": "open-model",
+ "eligible": true,
+ "estimated_cost_usd": 0.06,
+ "estimated_latency_ms": 420000
+ }
+ ],
+ "decided_at": "2026-10-05T09:00:01.000Z"
+ }
+ ],
+ "cancel": null,
+ "progress": null
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,192 @@
@@ -0,0 +1,192 @@
+# ADR-044: The Actor runtime — the task contract, adapter interface, modes, signals, verification and trust
+
+**Status:** Proposed 2026-10-05 (plan track T17). This is the build-order item immediately after the engine: plan
+T17's build order puts "the engine first, where it already runs — harness adapter interface in `openvibe-agents` with
+the first new adapters (OpenCode, Antigravity CLI), capability-aware routing, vision inputs and a local sandbox
+(headless browser + virtual desktop, screenshots)" ahead of "ADR-044 (task contract, adapter interface, modes,
+signals, verification, trust)", and the engine's harness work is paused where it stands. Contract: `platform.task@1`.
+Builds on ADR-034 §2 (projects and resource names), §5 (control and data plane) and §9 (metering), on ADR-046 (the
+placer, its objectives and the five trust classes), on ADR-012 (one Billing authority) and on ADR-043 (Node control
+levels and the local kill switch). For the owner's review.
+
+## Context and current evidence
+
+- **Plan T17** describes the product: "one API and one site where any task goes to the best agent system for it, the
+ way OpenRouter does it for models — and one console where people command every OpenVibe resource in plain
+ language." Its first layer is the task router: "`POST /v1/tasks` with a task, a mode and a budget → the planner
+ picks the backend, runs it, verifies the result, escalates or fails over, and answers with the result, the cost and
+ the explanation. Streaming progress, cancellation, webhooks."
+- **The router has no contract.** `contracts/platform/` holds the Fabric's offers, requirements, placement results,
+ rate cards, telemetry and usage samples, and the Run sibling's `platform.job@1` and `platform.job-frame@1`; none of
+ them is a task. There is no `task.v1.json`, no route contract for `POST /v1/tasks`, and no adapter shape written
+ down.
+- **There is no Actor service to own it.** `manifests/services/` holds the estate's service manifests; none is
+ Actor's. Actor
+ exists only as a product page, `manifests/products/openvibe.actor.json`, with `relationships.noRepo: true` — a
+ site description, not an authority. Every `platform.*` contract is owned by `network`, and this one follows them.
+- **The engine already runs.** The plan's engine is `openvibe-agents`, the harness that runs coding, research and
+ review jobs across cheap agents with review and hand-off. Plan T17 names it as where the harness adapter interface
+ is built first; that work is not done, so the decisions below describe an interface the engine does not implement
+ yet.
+- **The pieces the router composes exist as contracts.** `openvibe-sdk/placement` is the one placer (ADR-046 §1);
+ `platform.placement-result@1` is its explanation; `platform.rate-card@1`, `platform.cost-snapshot@1`,
+ `platform.telemetry-sample@1` and `platform.usage-sample@1` are the cost and signal vocabulary (ADR-046 §§2–3);
+ `platform.runtime-class@1`, `platform.job@1` and `platform.job-frame@1` are Run's execution shape (T14); the
+ `agent` subject is already in `identity/subject-ref@1`.
+- **Actor's backend classes** are fixed by the plan: "agent platform, OpenVibe runtime, **Codes**, **Node**, **Run**.
+ Coding work is exposed to Actor **through Codes** — one coding-harness router in the estate, not two."
+
+## Decision
+
+### 1. One task contract: `platform.task@1`
+
+`contracts/platform/task.v1.json` (PLANNED, owner `network`) is one task at the router: the resource `POST /v1/tasks`
+creates and `GET /v1/tasks/{id}` reads. It carries `id` (`tsk_<ULID>`), `project_id`, `requester` (a person or an
+`agent` subject), the `task` text, the `mode`, the `budget`, the `state`, `created_at`/`finished_at`, the `result`,
+the `cost`, the `explanation` (one or more `platform.placement-result@1`, oldest first, an escalation appending one),
+the `progress` descriptor, the `cancel` record and the registered `webhooks`.
+
+- **`POST /v1/tasks` takes the task's creation subset** — `task`, `mode`, `budget`, and optionally `webhooks`. The
+ router mints `id`, sets `project_id` and `requester` from the token, and answers `state: queued`.
+- **It is the resource, not a device frame.** Run's `platform.job@1` is handed to a worker inside a
+ `platform.job-frame@1` link message because the link is OpenVibe.Node's WebSocket. Actor's link is HTTP and
+ server-sent events, so the frame vocabulary is not reused as a message type; `progress` and `explanation` carry
+ what the frames carry for a job. This is the shape, and whether Actor later needs an explicit stream-event contract
+ of its own (as `run.job-stream-event@1` is Run's) is left to the router's implementation.
+- **The contract is `status: planned`** until T17 ships: the catalog's not-yet-implemented value (there is no
+ `proposed` status), the same as the planned contracts of T14.
+
+### 2. The adapter interface
+
+An adapter is how Actor reaches one agent system, and the plan fixes what it declares: "Each adapter declares
+capabilities, limits, a revisioned rate card (never AI-written) and trust class; BYO keys for any backend." So an
+adapter has:
+
+- a **capability set** (which task classes it can take, including vision), checked before any mode or budget;
+- **limits** (context, concurrency, time, egress) merged stricter with the caller's, as `platform.job@1` limits are;
+- a **rate card** (`platform.rate-card@1`) with `source` and `verified_at`, revised by review and never written by an
+ AI on its own;
+- a **trust class** from ADR-046 §4;
+- **BYO key** support, so a caller can point the adapter at their own account and pay it directly.
+
+The plan puts the first adapters in the engine (OpenCode, Antigravity CLI) and demands that "a new agent platform is
+one adapter". The concrete interface — its field names, whether it is one manifest or a set of declared contracts,
+and how it is registered — is **not fixed by the plan and is left open**; this ADR fixes only what an adapter must
+declare, not the wire shape.
+
+### 3. Modes are planner objectives
+
+`mode` is one of `cheapest`, `balanced`, `best`, `fastest`, `private`; `balanced` is the default. The plan is
+explicit that "a mode is a planner objective, not a hard-coded list", so a mode is stated to the placer, not
+translated into a backend by hand. Four modes map onto ADR-046's `platform.placement-result@1` objectives
+directly: `cheapest → cheapest`, `balanced → balanced`, `fastest → lowest-latency`, `private → private`. `best`
+does **not** map to `correctness`: the SDK's `correctness` objective only preserves `req.authority` or the current
+placement, so for a new task with neither it ranks like `balanced`, and `high-reliability` has no branch either —
+`openvibe-sdk/placement` has no quality ranking that expresses "the highest chance of a right answer first time".
+What `best` maps to is therefore left as an open question below; the placer may refine the rest, and the exact
+tie-breaks inside a mode are left to `openvibe-sdk/placement`.
+
+### 4. Signals and auto-balancing
+
+Auto-balancing goes "through `openvibe-sdk/placement` over live signals per backend × task class; hysteresis keeps a
+task class unless another is clearly better; failover is immediate; every decision is stored and explained." The
+signals are the ones the Fabric already contracts: rate cards price a backend, `platform.telemetry-sample@1` reports
+its latency, health and status, and `platform.cost-snapshot@1` measures what a class actually cost against its
+alternatives. A capability the task needs is a hard filter before ranking, as `excluded()` is in the placer. The
+explanation is `platform.placement-result@1`, and it is part of the task, so "which agent ran, why it won, what else
+was considered and what it cost" survives the request.
+
+### 5. Verification and cascades
+
+"Cheap first, escalate on failure; cross-family checks; the task's own tests when it has them (code: tests; research:
+citations; actions: postconditions)." The task therefore has a `verifying` state between `running` and `succeeded`: a
+result that exists is checked before it is delivered, by a model family other than the one that produced it, and only
+then becomes `result`. A failed check moves the task to a stronger backend and appends a placement result; the plan
+puts the full cascade build after the router itself, so the first router may ship with a single check. A task that
+never passes ends `failed`; it never returns an unchecked result as `succeeded`.
+
+### 6. Trust
+
+ADR-046 §4's five classes apply unchanged: `first-party`, `user-owned`, `partner`, `community`, `external`, with
+`user-owned` eligible only when the task names it. `private` mode narrows the eligible set to backends that keep the
+data on OpenVibe — `first-party`, and a `user-owned` node only when the deployment allows it — which the plan
+describes as "only agents that keep your data on OpenVibe: its own runtime and open models on its hardware". Local
+execution on a person's own machine goes through OpenVibe.Node under ADR-043's control levels (Observe / Ask /
+Trusted / Full control), a local indicator and a kill switch the owner holds; Node's local policy always overrides a
+cloud command (plan T14).
+
+### 7. Progress, cancel and webhooks
+
+- **Progress** is `progress`: the server-sent-events `stream_url`, the task's own `last_seq`, and the event kinds
+ (`output`, `state`, `end`, the same three as `run.job-stream-event@1`). The stream is best-effort, as a job's stdout
+ is; the task record is the source of truth.
+- **Cancel** is `cancel`: `POST /v1/tasks/{id}/cancel` records who asked and when, the running backend is stopped as
+ its own contract allows (a Run job through `run.job.cancel`, a Node task through its link, an external platform
+ through its API), and the task ends `cancelled` with `finished_at` set. Cancel is not a promise that money already
+ spent is returned.
+- **Webhooks** are registered at creation: an HTTPS URL, the state changes to deliver, and an optional `secret_ref`
+ naming the project secret used to sign the delivery — the name, never the secret, as
+ `community.moderation-request@1` names a `webhook_url_ref` and ADR-043 keeps device credentials. A failed delivery
+ is retried with backoff and never delays the task.
+
+### 8. Cost and budgets
+
+`budget.per_task_usd` and `budget.per_day_usd` are hard limits the router never exceeds; a task that cannot be run
+inside `per_task_usd` fails (`actor.budget.exceeded`) instead of running, and `per_day_usd` caps the project's tasks
+in one UTC day. `cost.usd` is what the task was billed, to the cent, across every attempt, and the plan's money rule
+is "one key, one bill (Billing, T5), per-task cost reported to the cent; free tier from the bounded allowance". The
+metering is `platform.usage-sample@1`, written through Billing as Run's job seconds are.
+
+## Authority boundaries
+
+Actor plans and routes; it does not become an authority. The plan's own list of what it calls:
+
+| What Actor needs | Whose authority |
+|---|---|
+| Coding work | Codes (the one coding-harness router) |
+| Persistent conditions | Watch |
+| Cloud execution | Run (`platform.job@1` / `platform.job-frame@1`) |
+| Local execution and computer control | Node (ADR-043 control levels, kill switch) |
+| Resource control | Services (`common.resource-control-request@1`, ADR-048) |
+| Artifacts | Media |
+| Robots | Bot |
+| Progress and triggers | Events (ADR-042) |
+| Models | AI |
+
+## Out of scope
+
+- The router, the console (T17 layer 3), persistent agents, memory, schedules and the approval inbox.
+- The concrete adapter interface and its registration (decision 2 leaves it open).
+- Rankings from real traffic, templates, public agents and the MCP server (later T17 steps).
+- The Actor service manifest and its deploy: there is no `OpenVibe.Actor` repository yet, so the contract is owned by
+ `network` until one registers a service.
+
+## Open questions for the owner
+
+1. **The adapter interface's shape.** One contract with a discriminator, or one small contract per adapter kind? The
+ plan fixes what an adapter declares, not the wire shape.
+2. **Per-class success as a signal.** ADR-046 contracts telemetry and cost, but not a per-backend × task-class
+ success rate. Whether that is a new signal contract or an aggregation of `run.job.*`-style outcomes is open.
+3. **Whether a `user-owned` node may take other people's tasks** (ADR-046's own open question), which `private` mode
+ depends on.
+4. **Webhook delivery guarantees** — retry count, backoff and whether an exhausted webhook surfaces on the task.
+5. **Where an escalated task's partial results live** before it succeeds.
+6. **What `best` maps to.** No objective in `openvibe-sdk/placement` ranks quality: `correctness` only preserves
+ `req.authority` or the current placement and otherwise ranks like `balanced`, and `high-reliability` has no
+ branch. Until the placer grows a real quality ranking, `best → <objective>` is undecided.
+
+## Consequences
+
+- One contract for every task, whatever backend runs it; a new backend is an adapter, not a schema change.
+- Additive only: one new catalog id at 1.0.0, one id prefix (`tsk`), and a planned contract with no route and no
+ capability yet, so nothing in `generated/openapi/` changes.
+- The router can be built against a fixed task resource while the adapter interface stays open.
+- Rollback: consumers pin the previous release tag; nothing reads `platform.task@1` until T17's router is written.
+
+## What this ADR does not claim
+
+- No Actor service, router, adapter or `POST /v1/tasks` route exists. `platform.task@1` is a contract for work not yet
+ built.
+- The engine's harness adapter interface is not built; this ADR decides what an adapter declares, not its fields.
+- No backend is ranked, verified or metered by these rules yet; the contracts they rest on are themselves active but
+ unwired for this use.
@@ -42,6 +42,7 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
@@ -42,6 +42,7 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
| [ADR-036](ADR-036-run-execution-authority.md) | Run is an execution authority, not a Host detail: Host deploys persistent applications, Run executes isolated jobs/environments (`function`, `code`, `browser`, `linux`, `desktop`, `gpu`), placed by the Fabric on the cheapest capable offer; provider adapters are offers, not code paths; per-second metering, TTL, idle suspend, cancel, snapshot/resume, stdout streaming, artifacts via Media, limits, network policy and strong isolation | Proposed 2026-10-05 (plan T14; plan §8 lists it to write before its code, and Run's code is owner-blocked), for the owner's review |
| [ADR-036](ADR-036-run-execution-authority.md) | Run is an execution authority, not a Host detail: Host deploys persistent applications, Run executes isolated jobs/environments (`function`, `code`, `browser`, `linux`, `desktop`, `gpu`), placed by the Fabric on the cheapest capable offer; provider adapters are offers, not code paths; per-second metering, TTL, idle suspend, cancel, snapshot/resume, stdout streaming, artifacts via Media, limits, network policy and strong isolation | Proposed 2026-10-05 (plan T14; plan §8 lists it to write before its code, and Run's code is owner-blocked), for the owner's review |
| [ADR-042](ADR-042-events-fabric.md) | The events fabric: the delivery-policy class is the one vocabulary and TOPIC/QUEUE/STREAM a derived carrier class; PostgreSQL stays the record, carriers are transports that fail open to `pg-v1`; a delivery lease before any second worker; per-key ordering; the SDK planner places deliveries and explains them; JetStream for STREAM only; opaque cursors replace the global seq after one overlap release; hot/replay/archive tiers; amends ADR-004's NATS rejection | Accepted 2026-09-29 (plan T7) |
| [ADR-042](ADR-042-events-fabric.md) | The events fabric: the delivery-policy class is the one vocabulary and TOPIC/QUEUE/STREAM a derived carrier class; PostgreSQL stays the record, carriers are transports that fail open to `pg-v1`; a delivery lease before any second worker; per-key ordering; the SDK planner places deliveries and explains them; JetStream for STREAM only; opaque cursors replace the global seq after one overlap release; hot/replay/archive tiers; amends ADR-004's NATS rejection | Accepted 2026-09-29 (plan T7) |
| [ADR-043](ADR-043-bot-devices-and-control.md) | Bot — devices, pairing, control and safety: robots, devices and credentials are separate (one hashed, rotatable, revocable device credential); pairing is a one-time code; one outbound WebSocket per device with absolute deadlines, device-side stops and a latched e-stop; robot profiles rather than rows; owner/operators/viewers with a timed public queue; everything audited; OpenVibe.Bot is its own service | Accepted 2026-09-29 (plan track T15, "Bot complete"; owner direction the same evening: openvibe.bot is an open control panel for robots, streaming optional; the first robots are the owner's Adeept 4WD Smart Car for Raspberry Pi and a Cozmo-based robot) |
| [ADR-043](ADR-043-bot-devices-and-control.md) | Bot — devices, pairing, control and safety: robots, devices and credentials are separate (one hashed, rotatable, revocable device credential); pairing is a one-time code; one outbound WebSocket per device with absolute deadlines, device-side stops and a latched e-stop; robot profiles rather than rows; owner/operators/viewers with a timed public queue; everything audited; OpenVibe.Bot is its own service | Accepted 2026-09-29 (plan track T15, "Bot complete"; owner direction the same evening: openvibe.bot is an open control panel for robots, streaming optional; the first robots are the owner's Adeept 4WD Smart Car for Raspberry Pi and a Cozmo-based robot) |
+| [ADR-044](ADR-044-actor-runtime.md) | The Actor runtime: one `platform.task@1` task (`POST /v1/tasks`) with a mode, a per-task and per-day budget, result, cost and explanation (a chain of `platform.placement-result@1`); adapters declare capabilities, limits, a reviewed rate card, a trust class and BYO keys; modes are `openvibe-sdk/placement` objectives; live signals auto-balance with hysteresis and immediate failover; a `verifying` state holds cheap-first cross-family checks and the task's own tests; the five ADR-046 trust classes, `private` narrowing to OpenVibe-only, Node control levels underneath; progress over SSE, cancel and webhooks; owner `network` (no Actor service manifest exists yet) | Proposed 2026-10-05 (plan T17), for the owner's review |
| [ADR-045](ADR-045-node-universal-runtime.md) | The Node is OpenVibe's universal runtime, not a Bot/Actor helper: one checksummed release binary and one control link serving the execution worker (`worker:<class>`, per ADR-046 §5), computer control under device-set control levels with a local kill switch and recording, and cache/storage/delivery; local policy always overrides cloud commands; ADR-043 stays the robot/device half | Proposed 2026-10-05 (plan T14 and §8 revision; consistent with ADR-046 and ADR-048, amends ADR-043's credential paragraph), for the owner's review |
| [ADR-045](ADR-045-node-universal-runtime.md) | The Node is OpenVibe's universal runtime, not a Bot/Actor helper: one checksummed release binary and one control link serving the execution worker (`worker:<class>`, per ADR-046 §5), computer control under device-set control levels with a local kill switch and recording, and cache/storage/delivery; local policy always overrides cloud commands; ADR-043 stays the robot/device half | Proposed 2026-10-05 (plan T14 and §8 revision; consistent with ADR-046 and ADR-048, amends ADR-043's credential paragraph), for the owner's review |
| [ADR-046](ADR-046-universal-adaptive-fabric.md) | The universal adaptive fabric: offers, requirements, placement results and signed plans through one placer (`openvibe-sdk/placement`); health, cost, telemetry and usage samples; five trust classes, with `user-owned` eligible only when a workload names it; `worker:<class>` Node capabilities; the data plane keeps running on the last valid signed plan; T6 AI the reference consumer | Proposed 2026-10-03 (plan T1 step 3), for the owner's review |
| [ADR-046](ADR-046-universal-adaptive-fabric.md) | The universal adaptive fabric: offers, requirements, placement results and signed plans through one placer (`openvibe-sdk/placement`); health, cost, telemetry and usage samples; five trust classes, with `user-owned` eligible only when a workload names it; `worker:<class>` Node capabilities; the data plane keeps running on the last valid signed plan; T6 AI the reference consumer | Proposed 2026-10-03 (plan T1 step 3), for the owner's review |
| [ADR-048](ADR-048-services-control-plane.md) | The Services control plane: Services aggregates authorities and owns no other service's rows; every console action is a `common.resource-control-request@1` to the owning authority, answered with `common.resource-control-result@1` (idempotency keys, `confirmation_id` for sensitive actions); OVRN `ovrn:<service>:<project_id>:<type>/<id>` (`common.resource-name@1`) is the one cross-service resource name | Accepted 2026-10-04 (plan T13) |
| [ADR-048](ADR-048-services-control-plane.md) | The Services control plane: Services aggregates authorities and owns no other service's rows; every console action is a `common.resource-control-request@1` to the owning authority, answered with `common.resource-control-result@1` (idempotency keys, `confirmation_id` for sensitive actions); OVRN `ovrn:<service>:<project_id>:<type>/<id>` (`common.resource-name@1`) is the one cross-service resource name | Accepted 2026-10-04 (plan T13) |
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },
+ "task": "Summarize the release notes.",
+ "mode": "balanced",
+ "budget": { "per_task_usd": 5 },
+ "state": "queued",
+ "created_at": "2026-10-05T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },
+ "task": "Draft a launch announcement for the new pricing page.",
+ "mode": "balanced",
+ "budget": { "per_task_usd": 2, "per_day_usd": 20 },
+ "state": "cancelled",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "finished_at": "2026-10-05T09:02:30.000Z",
+ "result": null,
+ "cost": { "usd": 0.01 }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },
+ "task": "Summarize the release notes.",
+ "mode": "balanced",
+ "budget": { "per_task_usd": 5, "per_day_usd": 50 },
+ "state": "failed",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "finished_at": "2026-10-05T09:01:00.000Z",
+ "result": { "partial": "a half-written brief" },
+ "cost": { "usd": 0.02 }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
+{
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },
+ "mode": "balanced",
+ "budget": { "per_task_usd": 5, "per_day_usd": 50 },
+ "state": "queued",
+ "created_at": "2026-10-05T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },
+ "task": "Summarize the release notes.",
+ "mode": "balanced",
+ "budget": { "per_task_usd": -1, "per_day_usd": 50 },
+ "state": "queued",
+ "created_at": "2026-10-05T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },
+ "task": "Summarize the release notes.",
+ "mode": "balanced",
+ "budget": { "per_task_usd": 5, "per_day_usd": 50 },
+ "state": "queued",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "webhooks": [
+ { "url": "http://example.com/hooks/actor", "events": ["succeeded"] }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+{
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": { "type": "agent", "id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPT" },
+ "task": "Check this week's outage report and summarize the causes.",
+ "mode": "cheapest",
+ "budget": { "per_task_usd": 1, "per_day_usd": 10 },
+ "state": "succeeded",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "finished_at": "2026-10-05T09:06:00.000Z",
+ "cost": { "usd": 0.06 },
+ "explanation": [
+ {
+ "selected": "open-model",
+ "objective": "cheapest",
+ "candidates": [{ "id": "open-model", "eligible": true }],
+ "decided_at": "2026-10-05T09:00:01.000Z"
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },
+ "task": "Summarize the release notes.",
+ "mode": "smart",
+ "budget": { "per_task_usd": 5, "per_day_usd": 50 },
+ "state": "queued",
+ "created_at": "2026-10-05T09:00:00.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
+{
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },
+ "task": "Draft a launch announcement for the new pricing page.",
+ "mode": "balanced",
+ "budget": { "per_task_usd": 2, "per_day_usd": 20 },
+ "state": "cancelled",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "finished_at": "2026-10-05T09:02:30.000Z",
+ "result": null,
+ "cost": { "usd": 0.01 },
+ "explanation": [
+ {
+ "selected": "hosted-agent",
+ "objective": "balanced",
+ "reasons": ["writing task; first attempt still running when the caller cancelled"],
+ "candidates": [{ "id": "hosted-agent", "eligible": true, "estimated_cost_usd": 0.31 }],
+ "decided_at": "2026-10-05T09:00:01.000Z"
+ }
+ ],
+ "cancel": {
+ "requested_at": "2026-10-05T09:02:29.000Z",
+ "requested_by": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" }
+ },
+ "progress": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+{
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },
+ "task": "Research five competitors of my shop and write a one-page brief with sources.",
+ "mode": "balanced",
+ "budget": { "per_task_usd": 5, "per_day_usd": 50 },
+ "state": "queued",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "webhooks": [
+ { "url": "https://example.com/hooks/actor", "events": ["succeeded", "failed"], "secret_ref": "ACTOR_WEBHOOK_SECRET" }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS" },
+ "task": "Fix the failing thumbnail test in OpenVibe.Media and open a PR.",
+ "mode": "best",
+ "budget": { "per_task_usd": 5, "per_day_usd": 50 },
+ "state": "running",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "progress": {
+ "stream_url": "https://openvibe.actor/v1/tasks/tsk_01JAB2C3D4E5F6G7H8J9K0MNPR/events",
+ "last_seq": 12,
+ "events": ["output", "state", "end"]
+ },
+ "explanation": [
+ {
+ "selected": "openvibe-runtime",
+ "objective": "correctness",
+ "reasons": ["the task is code; the Codes adapter exposes a code harness"],
+ "candidates": [{ "id": "openvibe-runtime", "eligible": true, "estimated_cost_usd": 0.06 }],
+ "decided_at": "2026-10-05T09:00:01.000Z"
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": { "type": "agent", "id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPT" },
+ "task": "Check this week's outage report and summarize the causes.",
+ "mode": "cheapest",
+ "budget": { "per_task_usd": 1, "per_day_usd": 10 },
+ "state": "succeeded",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "finished_at": "2026-10-05T09:06:00.000Z",
+ "result": { "brief": "Two outages; both were a saturated connection pool.", "citations": ["https://openvibe.status/incidents/41"] },
+ "cost": { "usd": 0.06, "free_allowance_used": 0.06 },
+ "explanation": [
+ {
+ "selected": "open-model",
+ "objective": "cheapest",
+ "reasons": ["research task; the checks are citations, met by the cheapest candidate"],
+ "candidates": [{ "id": "open-model", "eligible": true, "estimated_cost_usd": 0.06 }],
+ "decided_at": "2026-10-05T09:00:01.000Z"
+ }
+ ],
+ "cancel": null,
+ "progress": null
+}
@@ -64599,6 +64599,439 @@
@@ -64599,6 +64599,439 @@
]
]
}
}
},
},
+ "platform.task": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/platform/task.v1.json",
+ "title": "Task",
+ "description": "platform.task@1 (PLANNED; plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /v1/tasks creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered adapters, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /v1/tasks/{id} reads the same resource. Progress streams as server-sent events from `progress.stream_url` (a task's own counter in `progress.last_seq`, as platform.job-frame@1 counts on a link); `cancel` records that a cancel was requested (POST /v1/tasks/{id}/cancel), and `webhooks` are the registered endpoints the router delivers each state change to. No Actor service exists yet: OpenVibe.Actor is a product page (manifests/products/openvibe.actor.json, noRepo) with no service manifest, so the contract is owned by `network`, like every other platform.* contract, and stays PLANNED until T17 ships. This v1 contract may grow optional fields.",
+ "$comment": "Additive optional fields may extend this v1 contract. It is the task resource, not a device frame: Actor's link is HTTP/SSE where Run's is the Node WebSocket.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "project_id",
+ "requester",
+ "task",
+ "mode",
+ "budget",
+ "state",
+ "created_at"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^tsk_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "Minted by the router (tsk_<ULID>): the idempotency key of the task and the stem of its usage readings, as platform.job@1's id is for a job. Never reused."
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's."
+ },
+ "requester": {
+ "description": "The person or agent the task belongs to: a user subject for a person, an agent subject for an agent acting under the grants its owner delegated (roadmap WS-Z2).",
+ "allOf": [
+ {
+ "$ref": "../identity/subject-ref.v1.json"
+ }
+ ]
+ },
+ "task": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100000,
+ "description": "The instruction in plain words, exactly as the caller gave it. Not a backend command and not a model prompt: the planner turns it into one."
+ },
+ "mode": {
+ "type": "string",
+ "enum": [
+ "cheapest",
+ "balanced",
+ "best",
+ "fastest",
+ "private"
+ ],
+ "default": "balanced",
+ "description": "The plan's five modes, in contract spelling: Cheapest, Balanced (the default), Best, Fastest and Private are `cheapest`, `balanced`, `best`, `fastest` and `private`. A mode is the planner objective, never a fixed backend list (plan T17). `cheapest`: the lowest expected cost that still meets the task class's quality bar. `balanced` (the default): cost, quality and speed weighed together. `best`: the highest chance of a right answer first time. `fastest`: the shortest time to a checked result. `private`: only backends that keep the data on OpenVibe (first-party, and a user-owned node only when the task names it), per ADR-046."
+ },
+ "budget": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "Hard limits the router never exceeds (plan T17: 'a hard limit per task and per day'). A task whose cheapest eligible backend would take it over `per_task_usd` ends failed (actor.budget.exceeded) rather than running; `per_day_usd` caps the project's tasks in one UTC day and a new task waits or fails, it never overruns.",
+ "required": [
+ "per_task_usd",
+ "per_day_usd"
+ ],
+ "properties": {
+ "per_task_usd": {
+ "type": "number",
+ "minimum": 0
+ },
+ "per_day_usd": {
+ "type": "number",
+ "minimum": 0
+ }
+ }
+ },
+ "state": {
+ "type": "string",
+ "enum": [
+ "queued",
+ "running",
+ "verifying",
+ "succeeded",
+ "failed",
+ "cancelled"
+ ],
+ "description": "queued: stored, waiting for the planner. running: a backend is working, or a cascade is between attempts. verifying: a result exists and a check (the task's own tests, a citation check, a postcondition) runs before it is delivered. succeeded, failed and cancelled are the end states; an end state never changes."
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "When the task reached its end state; null before."
+ },
+ "result": {
+ "description": "The checked deliverable (any JSON, as a platform.job@1 `result` is): for a code task a diff or branch reference, for research a brief with citations, for an action a postcondition report. Never a credential and never raw sandbox output. Null (or absent) in every state but succeeded, which the allOf below enforces."
+ },
+ "cost": {
+ "anyOf": [
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "usd"
+ ],
+ "properties": {
+ "usd": {
+ "type": "number",
+ "minimum": 0,
+ "description": "What this task was billed, to the cent, across every attempt it made."
+ },
+ "free_allowance_used": {
+ "type": "number",
+ "minimum": 0,
+ "description": "The part of `usd` covered by the project's free allowance; metered as platform.usage-sample@1."
+ }
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Present once the task has run an attempt (also when it ends failed or cancelled); null while queued. One key, one bill (plan T17), through Billing (ADR-012)."
+ },
+ "explanation": {
+ "anyOf": [
+ {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "placement-result.v1.json"
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "One platform.placement-result@1 per backend the planner tried, oldest first, an escalation appending a new one: which backend won, why and what else was considered, from openvibe-sdk/placement (ADR-046 §1). Null while queued."
+ },
+ "progress": {
+ "anyOf": [
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "stream_url",
+ "last_seq"
+ ],
+ "properties": {
+ "stream_url": {
+ "type": "string",
+ "format": "uri",
+ "description": "The text/event-stream endpoint that carries this task's progress. Each event mirrors platform.job-frame@1's job_stdout/state shape: a state change, an output chunk, or the end."
+ },
+ "last_seq": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "The task's own event counter, from 0 before the first event; a reconnecting reader resumes after it, as a job frame's seq counts on one link."
+ },
+ "events": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "output",
+ "state",
+ "end"
+ ]
+ },
+ "description": "The event kinds the stream delivers, run.job-stream-event@1's own three."
+ }
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Streaming progress (plan T17). Null before the router opens a stream and after the end."
+ },
+ "cancel": {
+ "anyOf": [
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "requested_at"
+ ],
+ "properties": {
+ "requested_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When POST /v1/tasks/{id}/cancel was accepted. The running backend is stopped as its own contract allows; the state becomes cancelled and `finished_at` is set."
+ },
+ "requested_by": {
+ "$ref": "../identity/subject-ref.v1.json"
+ }
+ }
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Cancellation (plan T17): null until a cancel is requested, then it records who asked and when; the task's outcome is `state`."
+ },
+ "webhooks": {
+ "type": "array",
+ "maxItems": 8,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "url",
+ "events"
+ ],
+ "properties": {
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://",
+ "description": "The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused."
+ },
+ "events": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "enum": [
+ "queued",
+ "running",
+ "verifying",
+ "succeeded",
+ "failed",
+ "cancelled"
+ ]
+ },
+ "description": "The state changes delivered; the same names as `state`."
+ },
+ "secret_ref": {
+ "type": "string",
+ "pattern": "^[A-Z0-9_]+$",
+ "description": "The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference."
+ }
+ }
+ },
+ "description": "Webhooks (plan T17), registered at creation. A delivery that fails is retried with backoff and never delays the task; the task's own record is the source of truth."
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "required": [
+ "state"
+ ],
+ "properties": {
+ "state": {
+ "const": "succeeded"
+ }
+ }
+ },
+ "then": {
+ "required": [
+ "result",
+ "cost",
+ "explanation"
+ ],
+ "properties": {
+ "result": {
+ "not": {
+ "type": "null"
+ }
+ },
+ "cost": {
+ "type": "object"
+ },
+ "explanation": {
+ "type": "array"
+ }
+ }
+ },
+ "else": {
+ "properties": {
+ "result": {
+ "type": "null"
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "required": [
+ "state"
+ ],
+ "properties": {
+ "state": {
+ "const": "cancelled"
+ }
+ }
+ },
+ "then": {
+ "required": [
+ "cancel"
+ ]
+ }
+ }
+ ],
+ "examples": [
+ {
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
+ },
+ "task": "Research five competitors of my shop and write a one-page brief with sources.",
+ "mode": "balanced",
+ "budget": {
+ "per_task_usd": 5,
+ "per_day_usd": 50
+ },
+ "state": "queued",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "webhooks": [
+ {
+ "url": "https://example.com/hooks/actor",
+ "events": [
+ "succeeded",
+ "failed"
+ ],
+ "secret_ref": "ACTOR_WEBHOOK_SECRET"
+ }
+ ]
+ },
+ {
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPS"
+ },
+ "task": "Fix the failing thumbnail test in OpenVibe.Media and open a PR.",
+ "mode": "best",
+ "budget": {
+ "per_task_usd": 5,
+ "per_day_usd": 50
+ },
+ "state": "running",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "progress": {
+ "stream_url": "https://openvibe.actor/v1/tasks/tsk_01JAB2C3D4E5F6G7H8J9K0MNPR/events",
+ "last_seq": 12,
+ "events": [
+ "output",
+ "state",
+ "end"
+ ]
+ },
+ "explanation": [
+ {
+ "selected": "openvibe-runtime",
+ "objective": "correctness",
+ "reasons": [
+ "the task is code; the Codes adapter exposes a code harness"
+ ],
+ "candidates": [
+ {
+ "id": "openvibe-runtime",
+ "eligible": true,
+ "estimated_cost_usd": 0.06,
+ "estimated_latency_ms": 180000
+ }
+ ],
+ "decided_at": "2026-10-05T09:00:01.000Z"
+ }
+ ]
+ },
+ {
+ "id": "tsk_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "agent",
+ "id": "agt_01JAB2C3D4E5F6G7H8J9K0MNPT"
+ },
+ "task": "Check this week's outage report and summarize the causes.",
+ "mode": "cheapest",
+ "budget": {
+ "per_task_usd": 1,
+ "per_day_usd": 10
+ },
+ "state": "succeeded",
+ "created_at": "2026-10-05T09:00:00.000Z",
+ "finished_at": "2026-10-05T09:06:00.000Z",
+ "result": {
+ "brief": "Two outages; both were a saturated connection pool.",
+ "citations": [
+ "https://openvibe.status/incidents/41"
+ ]
+ },
+ "cost": {
+ "usd": 0.06,
+ "free_allowance_used": 0.06
+ },
+ "explanation": [
+ {
+ "selected": "open-model",
+ "objective": "cheapest",
+ "reasons": [
+ "research task; the checks are citations, met by the cheapest candidate"
+ ],
+ "candidates": [
+ {
+ "id": "open-model",
+ "eligible": true,
+ "estimated_cost_usd": 0.06,
+ "estimated_latency_ms": 420000
+ }
+ ],
+ "decided_at": "2026-10-05T09:00:01.000Z"
+ }
+ ],
+ "cancel": null,
+ "progress": null
+ }
+ ]
+ }
+ },
"events.delivery-policy": {
"events.delivery-policy": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "events",
"owner": "events",
@@ -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.100.0",
- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.Network API",
"title": "OpenVibe.Network API",
- "version": "0.100.0",
- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.101.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": "OpenRe.Stream API",
"title": "OpenRe.Stream API",
- "version": "0.100.0",
- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.101.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.100.0",
- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.100.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.",
+ "version": "0.101.0",
+ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.101.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"
@@ -52604,6 +52604,808 @@ export type JobFrame =
@@ -52604,6 +52604,808 @@ export type JobFrame =
};
};
};
};
+/** [email protected] (owner: network) */
+/**
+ * platform.task@1 (PLANNED; plan T17, ADR-044): one task at OpenVibe.Actor's task router. POST /v1/tasks creates it from a plain-language `task` plus a `mode` and a `budget`; the router mints `id`, sets `state` and `created_at`, picks a backend from the registered adapters, runs the task, verifies the result, escalates or fails over, and answers with `result`, `cost` and `explanation`. GET /v1/tasks/{id} reads the same resource. Progress streams as server-sent events from `progress.stream_url` (a task's own counter in `progress.last_seq`, as platform.job-frame@1 counts on a link); `cancel` records that a cancel was requested (POST /v1/tasks/{id}/cancel), and `webhooks` are the registered endpoints the router delivers each state change to. No Actor service exists yet: OpenVibe.Actor is a product page (manifests/products/openvibe.actor.json, noRepo) with no service manifest, so the contract is owned by `network`, like every other platform.* contract, and stays PLANNED until T17 ships. This v1 contract may grow optional fields.
+ */
+export type Task = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * Minted by the router (tsk_<ULID>): the idempotency key of the task and the stem of its usage readings, as platform.job@1's id is for a job. Never reused.
+ */
+ id: string;
+ /**
+ * The project the caller's token names (ADR-034 §5): budgets, usage and the bill are the project's.
+ */
+ project_id: string;
+ /**
+ * The person or agent the task belongs to: a user subject for a person, an agent subject for an agent acting under the grants its owner delegated (roadmap WS-Z2).
+ */
+ requester: SubjectRef;
+ /**
+ * The instruction in plain words, exactly as the caller gave it. Not a backend command and not a model prompt: the planner turns it into one.
+ */
+ task: string;
+ /**
+ * The plan's five modes, in contract spelling: Cheapest, Balanced (the default), Best, Fastest and Private are `cheapest`, `balanced`, `best`, `fastest` and `private`. A mode is the planner objective, never a fixed backend list (plan T17). `cheapest`: the lowest expected cost that still meets the task class's quality bar. `balanced` (the default): cost, quality and speed weighed together. `best`: the highest chance of a right answer first time. `fastest`: the shortest time to a checked result. `private`: only backends that keep the data on OpenVibe (first-party, and a user-owned node only when the task names it), per ADR-046.
+ */
+ mode: "cheapest" | "balanced" | "best" | "fastest" | "private";
+ /**
+ * Hard limits the router never exceeds (plan T17: 'a hard limit per task and per day'). A task whose cheapest eligible backend would take it over `per_task_usd` ends failed (actor.budget.exceeded) rather than running; `per_day_usd` caps the project's tasks in one UTC day and a new task waits or fails, it never overruns.
+ */
+ budget: {
+ per_task_usd: number;
+ per_day_usd: number;
+ };
+ /**
+ * queued: stored, waiting for the planner. running: a backend is working, or a cascade is between attempts. verifying: a result exists and a check (the task's own tests, a citation check, a postcondition) runs before it is delivered. succeeded, failed and cancelled are the end states; an end state never changes.
+ */
+ state: "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled";
+ created_at: string;
+ /**
+ * When the task reached its end state; null before.
+ */
+ finished_at?: string | null;
+ /**
+ * The checked deliverable (any JSON, as a platform.job@1 `result` is): for a code task a diff or branch reference, for research a brief with citations, for an action a postcondition report. Never a credential and never raw sandbox output. Null (or absent) in every state but succeeded, which the allOf below enforces.
+ */
+ result?: {
+ [k: string]: unknown | undefined;
+ };
+ /**
+ * Present once the task has run an attempt (also when it ends failed or cancelled); null while queued. One key, one bill (plan T17), through Billing (ADR-012).
+ */
+ cost?: {
+ /**
+ * What this task was billed, to the cent, across every attempt it made.
+ */
+ usd: number;
+ /**
+ * The part of `usd` covered by the project's free allowance; metered as platform.usage-sample@1.
+ */
+ free_allowance_used?: number;
+ } | null;
+ /**
+ * One platform.placement-result@1 per backend the planner tried, oldest first, an escalation appending a new one: which backend won, why and what else was considered, from openvibe-sdk/placement (ADR-046 §1). Null while queued.
+ */
+ explanation?: [PlacementResult, ...PlacementResult[]] | null;
+ /**
+ * Streaming progress (plan T17). Null before the router opens a stream and after the end.
+ */
+ progress?: {
+ /**
+ * The text/event-stream endpoint that carries this task's progress. Each event mirrors platform.job-frame@1's job_stdout/state shape: a state change, an output chunk, or the end.
+ */
+ stream_url: string;
+ /**
+ * The task's own event counter, from 0 before the first event; a reconnecting reader resumes after it, as a job frame's seq counts on one link.
+ */
+ last_seq: number;
+ /**
+ * The event kinds the stream delivers, run.job-stream-event@1's own three.
+ */
+ events?: ("output" | "state" | "end")[];
+ } | null;
+ /**
+ * Cancellation (plan T17): null until a cancel is requested, then it records who asked and when; the task's outcome is `state`.
+ */
+ cancel?: {
+ /**
+ * When POST /v1/tasks/{id}/cancel was accepted. The running backend is stopped as its own contract allows; the state becomes cancelled and `finished_at` is set.
+ */
+ requested_at: string;
+ requested_by?: SubjectRef;
+ } | null;
+ /**
+ * Webhooks (plan T17), registered at creation. A delivery that fails is retried with backoff and never delays the task; the task's own record is the source of truth.
+ *
+ * @maxItems 8
+ */
+ webhooks?:
+ | []
+ | [
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ }
+ ]
+ | [
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ }
+ ]
+ | [
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ }
+ ]
+ | [
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ }
+ ]
+ | [
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ }
+ ]
+ | [
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ }
+ ]
+ | [
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ }
+ ]
+ | [
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ },
+ {
+ /**
+ * The HTTPS endpoint the router POSTs a state change to. A plain-HTTP URL is refused.
+ */
+ url: string;
+ /**
+ * The state changes delivered; the same names as `state`.
+ *
+ * @minItems 1
+ */
+ events: [
+ "queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled",
+ ...("queued" | "running" | "verifying" | "succeeded" | "failed" | "cancelled")[]
+ ];
+ /**
+ * The NAME of the project secret used to sign the delivery (an HMAC header), never the secret itself, as community.moderation-request@1 names a webhook_url_ref and ADR-043 keeps device credentials by reference.
+ */
+ secret_ref?: string;
+ }
+ ];
+};
+
/** [email protected] (owner: events) */
/** [email protected] (owner: events) */
/**
/**
* How an event type is carried (roadmap WS-Z3 task 2): its delivery class sets the minimum semantics, which a publisher's intent may raise and never lower; ordering is per key; the route planner picks the carrier.
* How an event type is carried (roadmap WS-Z3 task 2): its delivery class sets the minimum semantics, which a publisher's intent may raise and never lower; ordering is per key; the route planner picks the carrier.
@@ -6,7 +6,7 @@
@@ -6,7 +6,7 @@
const crypto = require('crypto');
const crypto = require('crypto');
const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
-const PREFIX = { user: 'usr', guest: 'gst', app: 'app', mod: 'mod', agent: 'agt', node: 'nod', event: 'evt', media: 'med', project: 'prj', confirmation: 'cnf', job: 'job', watch: 'wch', observation: 'wco', check: 'ckr' };
+const PREFIX = { user: 'usr', guest: 'gst', app: 'app', mod: 'mod', agent: 'agt', node: 'nod', event: 'evt', media: 'med', project: 'prj', confirmation: 'cnf', job: 'job', task: 'tsk', watch: 'wch', observation: 'wco', check: 'ckr' };
const SLUG_TYPES = new Set(['service', 'system']);
const SLUG_TYPES = new Set(['service', 'system']);
const ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
const ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
const SLUG_RE = /^[a-z][a-z0-9-]{1,39}$/;
const SLUG_RE = /^[a-z][a-z0-9-]{1,39}$/;
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.100.0",
+ "version": "0.101.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.100.0",
+ "version": "0.101.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.100.0",
+ "version": "0.101.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",
@@ -531,7 +531,7 @@ ok(nodeId.startsWith('nod_') && ids.principalSub({ type: 'node', id: nodeId }) =
@@ -531,7 +531,7 @@ ok(nodeId.startsWith('nod_') && ids.principalSub({ type: 'node', id: nodeId }) =
assert.throws(() => ids.principalSub({ type: 'node', id: 'nod_42' }), /principals/);
assert.throws(() => ids.principalSub({ type: 'node', id: 'nod_42' }), /principals/);
ok(!ids.SUBJECT_TYPES.includes('node') && ids.parseSubject(`node:${nodeId}`) === null, 'a node is a principal, never a subject');
ok(!ids.SUBJECT_TYPES.includes('node') && ids.parseSubject(`node:${nodeId}`) === null, 'a node is a principal, never a subject');
assert.throws(() => ids.principalSub({ type: 'user', id: ids.newId('user') }), /principals/);
assert.throws(() => ids.principalSub({ type: 'user', id: ids.newId('user') }), /principals/);
-for (const [kind, prefix] of [['watch', 'wch'], ['observation', 'wco'], ['check', 'ckr'], ['job', 'job']]) ok(ids.newId(kind).startsWith(`${prefix}_`), `newId(${kind}) is ${prefix}_<ULID>`);
+for (const [kind, prefix] of [['watch', 'wch'], ['observation', 'wco'], ['check', 'ckr'], ['job', 'job'], ['task', 'tsk']]) ok(ids.newId(kind).startsWith(`${prefix}_`), `newId(${kind}) is ${prefix}_<ULID>`);
ok(contracts.validate('media.media-ref', { media_id: ids.legacyMediaId('live', 'vod', 42) }).valid, 'legacyMediaId is a valid MediaRef');
ok(contracts.validate('media.media-ref', { media_id: ids.legacyMediaId('live', 'vod', 42) }).valid, 'legacyMediaId is a valid MediaRef');
ok(contracts.validate('events.event-envelope', { event_id: ids.newId('event'), event_type: 'network.user.created', version: 1, source: 'network', actor: { type: 'system', id: 'network' }, timestamp: new Date().toISOString(), subject: { type: 'user', id: ids.newId('user') }, payload: {} }).valid, 'newId(event) builds a valid envelope');
ok(contracts.validate('events.event-envelope', { event_id: ids.newId('event'), event_type: 'network.user.created', version: 1, source: 'network', actor: { type: 'system', id: 'network' }, timestamp: new Date().toISOString(), subject: { type: 'user', id: ids.newId('user') }, payload: {} }).valid, 'newId(event) builds a valid envelope');