openvibers/openvibe.contracts Commits

v0.63.0: project usage rollups (roadmap WS-N task 4, ADR-014). New common.usage-recorded@1: one closed hour (or day) of one developer project's use of one capability in one environment, as the owning service counted it: project_id, env, capability, optional dimension (job type or tool), unit, window, window_start/window_end, quantity, errors, error_codes (up to 20) and samples (up to 10 recent failures: time, code, status, trace id, job/event/run/object id), plus revision; totals, not deltas, so a re-sent window replaces the earlier one; never a subject, app, address, session, input or message. New event types tools.usage.recorded (a Tools satellite's hour of a project's jobs, per tools.job.create or tools.tool.run) and events.usage.recorded (events.app.publish in events with refusals as errors; events.app.subscribe in deliveries with failed attempts as errors), both common.usage-recorded@1; the Tools and Events manifests produce them and Network consumes both. New network.project-usage-result@1 for GET /api/v1/projects/:project/usage on Network (daily rows, totals, recorded quotas with what their window used, errors by code and recent sampled failures). Fixtures; npm test checks each <service>.usage.recorded is the common payload, owned by its service and consumed by Network, and that a rollup carries no identity and at most ten samples. Additive (compat vs v0.62.0: no breaking changes).

OP OpenVibers committed 26 Sept 2026 · 6f32032af9 · 65 files, +6136 −1328


Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex df14e649..4b621be9 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -6,6 +6,16 @@ the notes were in the tag and commit messages (`git tag -n1`).
## Unreleased
++## 0.63.0 — 2026-09-26
++
++**Project usage rollups** (roadmap WS-N task 4, ADR-014): what feeds the per-project dashboards on OpenVibe.Codes.
++- **New `common.usage-recorded@1`**: one closed window (an hour or a day) of one developer project's use of one capability in one environment, as the owning service counted it: `project_id`, `env`, `capability`, an optional `dimension` (a job type or tool id), `unit`, `window`, `window_start`, `window_end`, `quantity`, `errors`, `error_codes` (up to 20) and `samples` (up to 10 recent failures: time, code, status, trace id and a job, event, run or object id), plus `revision`. Totals, not deltas: a later rollup with the same key replaces the earlier one. It never names who did the work (no subject, app, address, session, input or message), and no producer emits one per request.
++- **New event types `tools.usage.recorded`** (a Tools satellite's hour of a project's jobs, unit `jobs`, per `tools.job.create` or `tools.tool.run` and job type or tool) **and `events.usage.recorded`** (Events: `events.app.publish` in `events`, refusals as errors; `events.app.subscribe` in `deliveries`, failed webhook attempts as errors). Both payloads are `common.usage-recorded@1`; envelope subject `{ type: project, id: <project_id> }`, visibility `internal`, priority `low`. The Tools and Events manifests produce them; Network consumes both.
++- **New `network.project-usage-result@1`**: what `GET /api/v1/projects/:project/usage?days=&env=` on Network answers a project's owner, admins and staff: daily rows and range totals per service, capability, unit and environment, the project's recorded quotas with what their current window used, and errors by code with the recent sampled failures.
++- `npm test` checks that each `<service>.usage.recorded` is the common payload, owned by its service and consumed by Network, and that a rollup carries no identity and at most ten samples.
++
++Additive.
++
## 0.62.0 — 2026-09-26
**Operator alerts** (roadmap WS-H task 11; the delivery path that Host's Prometheus alerts lacked).
ddiff --git a/README.md b/README.md
iindex 7f83fd68..cb1b76da 100644
---- a/README.md
++++ b/README.md
@@@ -95,6 +95,10 @@ const r = contracts.validate(`${env.event_type}@${env.version}`, env.payload);
**Redaction (ADR-026).** Any event may carry `payload.redacts` (`events.redaction-directive@1`), shaped `{ event_ids?, subject_type?, subject_ids? }` with up to 1000 ids each, to take back the producer's own earlier events. OpenVibe.Events rewrites each target into a tombstone at its original seq: `{ redacted: true, redacted_at, redacted_by }`, with the producer itself as actor. Naming another source's event refuses the whole batch with 403 `events.redaction_not_allowed`. A malformed directive gets 422 `events.invalid_redaction`. `chat.message.deleted` always carries one. Browsers are replayed only the `public` events of the last `REALTIME_PUBLIC_REPLAY_SECONDS` (default 300); an older cursor gets `event: gap` with reason `public_window`.
++## Project usage rollups (v0.63)
++
++The services that own a developer capability count each project's use of it and report one rollup per closed hour, never one event per request (roadmap WS-N task 4). Every `<service>.usage.recorded` event carries `common.usage-recorded@1`: project, environment, capability, optional dimension (job type, tool), unit, window, quantity, errors, errors by code and up to ten sampled failures with their trace ids. Emitted today: `tools.usage.recorded` (jobs) and `events.usage.recorded` (published events, webhook deliveries). OpenVibe.Network adds them up per project and day and answers `GET /api/v1/projects/:project/usage` (`network.project-usage-result@1`) to the project's owner and admins; OpenVibe.Codes shows it as the project's usage page. A rollup is totals, not a delta: the latest one for a key replaces the earlier, so a producer may re-send a window safely.
++
## Event delivery signatures
OpenVibe.Events POSTs each webhook delivery as `{ "event": <events.event-envelope@1>, "seq": n }`, keyed with the subscription secret:
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex f8bebe4c..329ac799 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -210,6 +210,16 @@
"status": "active",
"adr": "ADR-022"
},
++ {
++ "id": "common.usage-recorded",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "common/usage-recorded.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-014"
++ },
{
"id": "media.file-upload",
"version": "1.0.0",
@@@ -2249,6 +2259,26 @@
"status": "active",
"adr": "ADR-022"
},
++ {
++ "id": "tools.usage.recorded",
++ "version": "1.0.0",
++ "owner": "tools",
++ "schema": "events/payloads/tools.usage.recorded.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-014"
++ },
++ {
++ "id": "events.usage.recorded",
++ "version": "1.0.0",
++ "owner": "events",
++ "schema": "events/payloads/events.usage.recorded.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-014"
++ },
{
"id": "live.stream.started",
"version": "1.0.0",
@@@ -2513,6 +2543,16 @@
"status": "active",
"adr": "ADR-005"
},
++ {
++ "id": "network.project-usage-result",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "network/project-usage-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-014"
++ },
{
"id": "network.staff-list-result",
"version": "1.0.0",
ddiff --git a/contracts/common/usage-recorded.v1.json b/contracts/common/usage-recorded.v1.json
nnew file mode 100644
iindex 00000000..6d562ecc
---- /dev/null
++++ b/contracts/common/usage-recorded.v1.json
@@@ -0,0 +1,70 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/common/usage-recorded.v1.json",
++ "title": "UsageRecorded",
++ "description": "common.usage-recorded@1 (roadmap WS-N task 4, ADR-014): one rollup of a developer project's use of one capability in one environment over one closed window (an hour or a day), as the service that owns the capability counted it. The payload of every <service>.usage.recorded event; OpenVibe.Network adds them up per project and day for the project's dashboard on OpenVibe.Codes. The producer counts in the transaction that does its own accounting (a job's end, a stored event, a delivery attempt) and writes the rollup to its outbox after the window has closed: never one event per request. Totals, not deltas: a later event with the same key (source, project_id, env, capability, dimension, unit, window_start) replaces the earlier one, and a lower revision never replaces a higher one. Envelope: subject { type: project, id: <project_id> }, visibility internal, priority low, actor the producing service. Never carries a subject id, an address, a session, a request's input or content, or a file name.",
++ "type": "object",
++ "required": ["project_id", "env", "capability", "unit", "window", "window_start", "window_end", "quantity", "errors"],
++ "additionalProperties": false,
++ "properties": {
++ "project_id": { "type": "string", "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$", "description": "The developer project whose app token did the work." },
++ "env": { "enum": ["sandbox", "production"], "description": "The environment of that app token." },
++ "capability": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$",
++ "maxLength": 80,
++ "description": "The capability the usage counts against: tools.job.create, tools.tool.run, events.app.publish, events.app.subscribe."
++ },
++ "dimension": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "pattern": "^[a-z0-9][a-z0-9_.:-]*$",
++ "not": { "pattern": "^(usr|gst|app|mod|svc|job|evt|req)_" },
++ "description": "Optional finer key inside the capability, as the producer names it: a job type (img.process) or a tool id (image-resize). Never the id of a person, an app, a request or a job."
++ },
++ "unit": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$",
++ "description": "What quantity counts (jobs, events, deliveries, requests, bytes, tokens): the same word a Network project quota uses for its unit."
++ },
++ "window": { "enum": ["hour", "day"], "description": "The length of the window." },
++ "window_start": { "type": "string", "format": "date-time", "description": "UTC, on the hour (midnight for a day)." },
++ "window_end": { "type": "string", "format": "date-time", "description": "window_start plus the window, exclusive." },
++ "quantity": { "type": "integer", "minimum": 0, "description": "Units used in the window, by work that succeeded and work that failed alike." },
++ "errors": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "Operations in the window that failed or were refused: a job that failed, a publish refused with a problem, a webhook attempt without a 2xx. A refused operation may use no unit, so errors can exceed quantity."
++ },
++ "error_codes": {
++ "type": "object",
++ "maxProperties": 20,
++ "propertyNames": { "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$", "maxLength": 80 },
++ "additionalProperties": { "type": "integer", "minimum": 1 },
++ "description": "The errors by problem code (at most 20 codes; the rest are only in errors)."
++ },
++ "samples": {
++ "type": "array",
++ "maxItems": 10,
++ "description": "The last failures of the window, newest first, so a developer can find them in their own logs.",
++ "items": {
++ "type": "object",
++ "required": ["at", "code"],
++ "additionalProperties": false,
++ "properties": {
++ "at": { "type": "string", "format": "date-time" },
++ "code": { "type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$", "maxLength": 80 },
++ "status": { "type": "integer", "minimum": 100, "maximum": 599, "description": "The HTTP status the failure was answered with, when there was one." },
++ "trace_id": { "type": "string", "pattern": "^[0-9a-f]{32}$", "description": "The W3C trace id of the request (or event) that failed." },
++ "ref": {
++ "type": "string",
++ "pattern": "^(job|evt|run|med)_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject."
++ }
++ }
++ }
++ },
++ "revision": { "type": "integer", "minimum": 1, "description": "1 when absent. A producer that re-emits a window with corrected totals raises it." }
++ }
++}
ddiff --git a/contracts/events/payloads/events.usage.recorded.v1.json b/contracts/events/payloads/events.usage.recorded.v1.json
nnew file mode 100644
iindex 00000000..a5096964
---- /dev/null
++++ b/contracts/events/payloads/events.usage.recorded.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/events.usage.recorded.v1.json",
++ "title": "EventsUsageRecordedPayload",
++ "description": "events.usage.recorded v1 (roadmap WS-N task 4, OpenVibe.Events server/usage.js). One hour of a developer project's use of OpenVibe.Events in one environment: events.app.publish in unit events (quantity counts the events stored; errors the publish requests refused with a problem, such as 429 events.quota_exceeded or 403 events.type_not_allowed) and events.app.subscribe in unit deliveries (quantity counts webhook delivery attempts to the project's subscriptions; errors the attempts without a 2xx, as events.delivery.http_<status>, events.delivery.timeout or events.delivery.failed). Counted in the transaction that stores the events or records the attempt (a refusal right after it is answered); Events stores the rollup as its own event after the hour closes, in the same transaction that marks it sent. The payload is common.usage-recorded@1. Envelope: source events, subject { type: project, id: <project_id> }, visibility internal, priority low, actor service:events.",
++ "$ref": "../../common/usage-recorded.v1.json"
++}
ddiff --git a/contracts/events/payloads/tools.usage.recorded.v1.json b/contracts/events/payloads/tools.usage.recorded.v1.json
nnew file mode 100644
iindex 00000000..c6731bee
---- /dev/null
++++ b/contracts/events/payloads/tools.usage.recorded.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/tools.usage.recorded.v1.json",
++ "title": "ToolsUsageRecordedPayload",
++ "description": "tools.usage.recorded v1 (roadmap WS-N task 4, OpenVibe.Tools apps/_shared/jobs/usage.js). One hour of a developer project's jobs on one Tools satellite, per capability (tools.job.create for POST /api/v1/jobs, tools.tool.run for a tool run) and job type or tool (dimension), in unit jobs: quantity counts the jobs that ended (succeeded or failed; a cancelled job is not counted), errors the failed ones, by code, with the last failures' job ids and trace ids. Counted in the transaction that records a job's end; written to the satellite's outbox after the hour closes. Only jobs of developer-app tokens (they carry a project) are counted; sandbox jobs are counted under env sandbox. The payload is common.usage-recorded@1. Envelope: subject { type: project, id: <project_id> }, visibility internal, priority low, actor service:tools.",
++ "$ref": "../../common/usage-recorded.v1.json"
++}
ddiff --git a/contracts/network/project-usage-result.v1.json b/contracts/network/project-usage-result.v1.json
nnew file mode 100644
iindex 00000000..f7338d9f
---- /dev/null
++++ b/contracts/network/project-usage-result.v1.json
@@@ -0,0 +1,118 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/project-usage-result.v1.json",
++ "title": "ProjectUsageResult",
++ "description": "network.project-usage-result@1 (roadmap WS-N task 4, ADR-014): what GET /api/v1/projects/:project/usage?days=&env= on OpenVibe.Network answers a project's owner or admin (or staff): the project's usage per day, service, capability and unit, added up from the <service>.usage.recorded rollups (common.usage-recorded@1) the owning services emit after each hour closes; the project's recorded quotas with what the current window has used; and its recent failures with their codes and trace ids. Counts only, never who did what: no subject id, address, input or content.",
++ "type": "object",
++ "required": ["project_id", "env", "range", "generated_at", "last_recorded_at", "freshness", "totals", "daily", "quotas", "errors"],
++ "additionalProperties": false,
++ "$defs": {
++ "service": { "type": "string", "pattern": "^[a-z][a-z0-9-]{1,39}$" },
++ "capability": { "type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$", "maxLength": 80 },
++ "unit": { "type": "string", "pattern": "^[a-z][a-z0-9_]{0,31}$" },
++ "env": { "enum": ["sandbox", "production"] },
++ "code": { "type": "string", "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$", "maxLength": 80 },
++ "count": { "type": "integer", "minimum": 0 }
++ },
++ "properties": {
++ "project_id": { "type": "string", "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$" },
++ "env": { "enum": ["all", "sandbox", "production"], "description": "The environments counted (the env query parameter; all by default)." },
++ "range": {
++ "type": "object",
++ "required": ["days", "from", "to"],
++ "additionalProperties": false,
++ "properties": {
++ "days": { "type": "integer", "minimum": 1, "maximum": 90 },
++ "from": { "type": "string", "format": "date", "description": "First UTC day counted." },
++ "to": { "type": "string", "format": "date", "description": "Last UTC day counted (today)." }
++ }
++ },
++ "generated_at": { "type": "string", "format": "date-time" },
++ "last_recorded_at": { "type": ["string", "null"], "format": "date-time", "description": "When the newest rollup for this project arrived; null before the first." },
++ "freshness": { "type": "string", "maxLength": 300, "description": "How current the numbers are, in words (rollups arrive after each hour closes)." },
++ "totals": {
++ "type": "array",
++ "description": "The whole range per service, capability, unit and environment.",
++ "items": {
++ "type": "object",
++ "required": ["service", "capability", "unit", "env", "quantity", "errors"],
++ "additionalProperties": false,
++ "properties": {
++ "service": { "$ref": "#/$defs/service" }, "capability": { "$ref": "#/$defs/capability" }, "unit": { "$ref": "#/$defs/unit" },
++ "env": { "$ref": "#/$defs/env" }, "quantity": { "$ref": "#/$defs/count" }, "errors": { "$ref": "#/$defs/count" }
++ }
++ }
++ },
++ "daily": {
++ "type": "array",
++ "description": "One row per UTC day, service, capability, dimension, unit and environment that saw usage; days without any are absent. Newest day first.",
++ "items": {
++ "type": "object",
++ "required": ["day", "service", "capability", "dimension", "unit", "env", "quantity", "errors"],
++ "additionalProperties": false,
++ "properties": {
++ "day": { "type": "string", "format": "date" },
++ "service": { "$ref": "#/$defs/service" }, "capability": { "$ref": "#/$defs/capability" },
++ "dimension": { "type": ["string", "null"], "maxLength": 80 },
++ "unit": { "$ref": "#/$defs/unit" }, "env": { "$ref": "#/$defs/env" },
++ "quantity": { "$ref": "#/$defs/count" }, "errors": { "$ref": "#/$defs/count" }
++ }
++ }
++ },
++ "quotas": {
++ "type": "array",
++ "description": "The project's recorded quotas (GET /api/v1/projects/:project/quotas), each with what its current window has used, in every environment together. used is null where the rollups cannot tell (a minute or hour window, or a unit no service reports for that capability).",
++ "items": {
++ "type": "object",
++ "required": ["capability", "limit", "window", "unit", "enforced_by", "used", "remaining", "window_start", "note"],
++ "additionalProperties": false,
++ "properties": {
++ "capability": { "$ref": "#/$defs/capability" },
++ "limit": { "type": "integer", "minimum": 0 },
++ "window": { "enum": ["minute", "hour", "day", "month", "total"] },
++ "unit": { "$ref": "#/$defs/unit" },
++ "enforced_by": { "type": ["string", "null"], "description": "The audience of the service that enforces it (openvibe.<owner>)." },
++ "used": { "type": ["integer", "null"], "minimum": 0 },
++ "remaining": { "type": ["integer", "null"], "minimum": 0 },
++ "window_start": { "type": ["string", "null"], "format": "date-time", "description": "Start of the current window counted (today, this month); null for total and when used is null." },
++ "note": { "type": ["string", "null"], "maxLength": 300 }
++ }
++ }
++ },
++ "errors": {
++ "type": "object",
++ "required": ["total", "by_code", "recent"],
++ "additionalProperties": false,
++ "properties": {
++ "total": { "$ref": "#/$defs/count" },
++ "by_code": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": ["service", "capability", "code", "count"],
++ "additionalProperties": false,
++ "properties": { "service": { "$ref": "#/$defs/service" }, "capability": { "$ref": "#/$defs/capability" }, "code": { "$ref": "#/$defs/code" }, "count": { "$ref": "#/$defs/count" } }
++ }
++ },
++ "recent": {
++ "type": "array",
++ "maxItems": 50,
++ "description": "The newest failures the rollups sampled, newest first.",
++ "items": {
++ "type": "object",
++ "required": ["at", "env", "service", "capability", "code", "status", "trace_id", "ref"],
++ "additionalProperties": false,
++ "properties": {
++ "at": { "type": "string", "format": "date-time" },
++ "env": { "$ref": "#/$defs/env" }, "service": { "$ref": "#/$defs/service" }, "capability": { "$ref": "#/$defs/capability" },
++ "code": { "$ref": "#/$defs/code" },
++ "status": { "type": ["integer", "null"], "minimum": 100, "maximum": 599 },
++ "trace_id": { "type": ["string", "null"], "pattern": "^[0-9a-f]{32}$" },
++ "ref": { "type": ["string", "null"], "pattern": "^(job|evt|run|med)_[0-9A-HJKMNP-TV-Z]{26}$" }
++ }
++ }
++ }
++ }
++ }
++ }
++}
ddiff --git a/fixtures/common.usage-recorded/invalid/carries-an-address.json b/fixtures/common.usage-recorded/invalid/carries-an-address.json
nnew file mode 100644
iindex 00000000..25eaf14d
---- /dev/null
++++ b/fixtures/common.usage-recorded/invalid/carries-an-address.json
@@@ -0,0 +1,32 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "window": "hour",
++ "window_start": "2026-09-26T14:00:00.000Z",
++ "window_end": "2026-09-26T15:00:00.000Z",
++ "quantity": 42,
++ "errors": 3,
++ "error_codes": {
++ "tools.job.failed": 2,
++ "tools.job.timeout": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "code": "tools.job.timeout",
++ "status": 504,
++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ {
++ "at": "2026-09-26T14:20:13.001Z",
++ "code": "tools.job.failed",
++ "status": 422,
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ }
++ ],
++ "ip": "203.0.113.7"
++}
ddiff --git a/fixtures/common.usage-recorded/invalid/carries-the-app.json b/fixtures/common.usage-recorded/invalid/carries-the-app.json
nnew file mode 100644
iindex 00000000..9f1b9759
---- /dev/null
++++ b/fixtures/common.usage-recorded/invalid/carries-the-app.json
@@@ -0,0 +1,32 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "window": "hour",
++ "window_start": "2026-09-26T14:00:00.000Z",
++ "window_end": "2026-09-26T15:00:00.000Z",
++ "quantity": 42,
++ "errors": 3,
++ "error_codes": {
++ "tools.job.failed": 2,
++ "tools.job.timeout": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "code": "tools.job.timeout",
++ "status": 504,
++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ {
++ "at": "2026-09-26T14:20:13.001Z",
++ "code": "tools.job.failed",
++ "status": 422,
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ }
++ ],
++ "app_id": "app_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/common.usage-recorded/invalid/dimension-is-a-person.json b/fixtures/common.usage-recorded/invalid/dimension-is-a-person.json
nnew file mode 100644
iindex 00000000..0f48e954
---- /dev/null
++++ b/fixtures/common.usage-recorded/invalid/dimension-is-a-person.json
@@@ -0,0 +1,31 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.job.create",
++ "dimension": "usr_01jab2c3d4e5f6g7h8j9k0mnpq",
++ "unit": "jobs",
++ "window": "hour",
++ "window_start": "2026-09-26T14:00:00.000Z",
++ "window_end": "2026-09-26T15:00:00.000Z",
++ "quantity": 42,
++ "errors": 3,
++ "error_codes": {
++ "tools.job.failed": 2,
++ "tools.job.timeout": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "code": "tools.job.timeout",
++ "status": 504,
++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ {
++ "at": "2026-09-26T14:20:13.001Z",
++ "code": "tools.job.failed",
++ "status": 422,
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ }
++ ]
++}
ddiff --git a/fixtures/common.usage-recorded/invalid/error-code-not-an-id.json b/fixtures/common.usage-recorded/invalid/error-code-not-an-id.json
nnew file mode 100644
iindex 00000000..422ac597
---- /dev/null
++++ b/fixtures/common.usage-recorded/invalid/error-code-not-an-id.json
@@@ -0,0 +1,30 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "window": "hour",
++ "window_start": "2026-09-26T14:00:00.000Z",
++ "window_end": "2026-09-26T15:00:00.000Z",
++ "quantity": 42,
++ "errors": 3,
++ "error_codes": {
++ "Timeout": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "code": "tools.job.timeout",
++ "status": 504,
++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ {
++ "at": "2026-09-26T14:20:13.001Z",
++ "code": "tools.job.failed",
++ "status": 422,
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ }
++ ]
++}
ddiff --git a/fixtures/common.usage-recorded/invalid/negative-quantity.json b/fixtures/common.usage-recorded/invalid/negative-quantity.json
nnew file mode 100644
iindex 00000000..c22b4272
---- /dev/null
++++ b/fixtures/common.usage-recorded/invalid/negative-quantity.json
@@@ -0,0 +1,11 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.tool.run",
++ "unit": "jobs",
++ "window": "day",
++ "window_start": "2026-09-25T00:00:00.000Z",
++ "window_end": "2026-09-26T00:00:00.000Z",
++ "quantity": -1,
++ "errors": 0
++}
ddiff --git a/fixtures/common.usage-recorded/invalid/no-window-start.json b/fixtures/common.usage-recorded/invalid/no-window-start.json
nnew file mode 100644
iindex 00000000..0bf34521
---- /dev/null
++++ b/fixtures/common.usage-recorded/invalid/no-window-start.json
@@@ -0,0 +1,10 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.tool.run",
++ "unit": "jobs",
++ "window": "day",
++ "window_end": "2026-09-26T00:00:00.000Z",
++ "quantity": 0,
++ "errors": 0
++}
ddiff --git a/fixtures/common.usage-recorded/invalid/per-minute-window.json b/fixtures/common.usage-recorded/invalid/per-minute-window.json
nnew file mode 100644
iindex 00000000..c02c9765
---- /dev/null
++++ b/fixtures/common.usage-recorded/invalid/per-minute-window.json
@@@ -0,0 +1,11 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.tool.run",
++ "unit": "jobs",
++ "window": "minute",
++ "window_start": "2026-09-25T00:00:00.000Z",
++ "window_end": "2026-09-26T00:00:00.000Z",
++ "quantity": 0,
++ "errors": 0
++}
ddiff --git a/fixtures/common.usage-recorded/invalid/sample-with-message.json b/fixtures/common.usage-recorded/invalid/sample-with-message.json
nnew file mode 100644
iindex 00000000..64bd8d78
---- /dev/null
++++ b/fixtures/common.usage-recorded/invalid/sample-with-message.json
@@@ -0,0 +1,23 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "window": "hour",
++ "window_start": "2026-09-26T14:00:00.000Z",
++ "window_end": "2026-09-26T15:00:00.000Z",
++ "quantity": 42,
++ "errors": 3,
++ "error_codes": {
++ "tools.job.failed": 2,
++ "tools.job.timeout": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "code": "tools.job.failed",
++ "detail": "Cannot read holiday.jpg"
++ }
++ ]
++}
ddiff --git a/fixtures/common.usage-recorded/invalid/subject-in-sample.json b/fixtures/common.usage-recorded/invalid/subject-in-sample.json
nnew file mode 100644
iindex 00000000..bd661fae
---- /dev/null
++++ b/fixtures/common.usage-recorded/invalid/subject-in-sample.json
@@@ -0,0 +1,23 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "window": "hour",
++ "window_start": "2026-09-26T14:00:00.000Z",
++ "window_end": "2026-09-26T15:00:00.000Z",
++ "quantity": 42,
++ "errors": 3,
++ "error_codes": {
++ "tools.job.failed": 2,
++ "tools.job.timeout": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "code": "tools.job.failed",
++ "ref": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ }
++ ]
++}
ddiff --git a/fixtures/common.usage-recorded/invalid/unknown-env.json b/fixtures/common.usage-recorded/invalid/unknown-env.json
nnew file mode 100644
iindex 00000000..d0a8eecc
---- /dev/null
++++ b/fixtures/common.usage-recorded/invalid/unknown-env.json
@@@ -0,0 +1,11 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "staging",
++ "capability": "tools.tool.run",
++ "unit": "jobs",
++ "window": "day",
++ "window_start": "2026-09-25T00:00:00.000Z",
++ "window_end": "2026-09-26T00:00:00.000Z",
++ "quantity": 0,
++ "errors": 0
++}
ddiff --git a/fixtures/common.usage-recorded/valid/events-publish-refused.json b/fixtures/common.usage-recorded/valid/events-publish-refused.json
nnew file mode 100644
iindex 00000000..9d55e272
---- /dev/null
++++ b/fixtures/common.usage-recorded/valid/events-publish-refused.json
@@@ -0,0 +1,23 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "sandbox",
++ "capability": "events.app.publish",
++ "unit": "events",
++ "window": "hour",
++ "window_start": "2026-09-26T09:00:00.000Z",
++ "window_end": "2026-09-26T10:00:00.000Z",
++ "quantity": 1800,
++ "errors": 61,
++ "error_codes": {
++ "events.quota_exceeded": 60,
++ "events.type_not_allowed": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T09:59:58.120Z",
++ "code": "events.quota_exceeded",
++ "status": 429,
++ "trace_id": "0af7651916cd43dd8448eb211c80319c"
++ }
++ ]
++}
ddiff --git a/fixtures/common.usage-recorded/valid/minimal-day.json b/fixtures/common.usage-recorded/valid/minimal-day.json
nnew file mode 100644
iindex 00000000..65a62acc
---- /dev/null
++++ b/fixtures/common.usage-recorded/valid/minimal-day.json
@@@ -0,0 +1,11 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.tool.run",
++ "unit": "jobs",
++ "window": "day",
++ "window_start": "2026-09-25T00:00:00.000Z",
++ "window_end": "2026-09-26T00:00:00.000Z",
++ "quantity": 0,
++ "errors": 0
++}
ddiff --git a/fixtures/common.usage-recorded/valid/revised-totals.json b/fixtures/common.usage-recorded/valid/revised-totals.json
nnew file mode 100644
iindex 00000000..7e59dfde
---- /dev/null
++++ b/fixtures/common.usage-recorded/valid/revised-totals.json
@@@ -0,0 +1,12 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.tool.run",
++ "unit": "jobs",
++ "window": "day",
++ "window_start": "2026-09-25T00:00:00.000Z",
++ "window_end": "2026-09-26T00:00:00.000Z",
++ "quantity": 7,
++ "errors": 0,
++ "revision": 2
++}
ddiff --git a/fixtures/common.usage-recorded/valid/tools-jobs-hour.json b/fixtures/common.usage-recorded/valid/tools-jobs-hour.json
nnew file mode 100644
iindex 00000000..df4f4a70
---- /dev/null
++++ b/fixtures/common.usage-recorded/valid/tools-jobs-hour.json
@@@ -0,0 +1,31 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "window": "hour",
++ "window_start": "2026-09-26T14:00:00.000Z",
++ "window_end": "2026-09-26T15:00:00.000Z",
++ "quantity": 42,
++ "errors": 3,
++ "error_codes": {
++ "tools.job.failed": 2,
++ "tools.job.timeout": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "code": "tools.job.timeout",
++ "status": 504,
++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ {
++ "at": "2026-09-26T14:20:13.001Z",
++ "code": "tools.job.failed",
++ "status": 422,
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ }
++ ]
++}
ddiff --git a/fixtures/events.usage.recorded/invalid/carries-event-payload.json b/fixtures/events.usage.recorded/invalid/carries-event-payload.json
nnew file mode 100644
iindex 00000000..726fcbd9
---- /dev/null
++++ b/fixtures/events.usage.recorded/invalid/carries-event-payload.json
@@@ -0,0 +1,26 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "sandbox",
++ "capability": "events.app.publish",
++ "unit": "events",
++ "window": "hour",
++ "window_start": "2026-09-26T09:00:00.000Z",
++ "window_end": "2026-09-26T10:00:00.000Z",
++ "quantity": 1800,
++ "errors": 61,
++ "error_codes": {
++ "events.quota_exceeded": 60,
++ "events.type_not_allowed": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T09:59:58.120Z",
++ "code": "events.quota_exceeded",
++ "status": 429,
++ "trace_id": "0af7651916cd43dd8448eb211c80319c"
++ }
++ ],
++ "payload": {
++ "type": "app.p01jab.order.created"
++ }
++}
ddiff --git a/fixtures/events.usage.recorded/invalid/first-party-project.json b/fixtures/events.usage.recorded/invalid/first-party-project.json
nnew file mode 100644
iindex 00000000..2518ad33
---- /dev/null
++++ b/fixtures/events.usage.recorded/invalid/first-party-project.json
@@@ -0,0 +1,23 @@
++{
++ "project_id": "live",
++ "env": "sandbox",
++ "capability": "events.app.publish",
++ "unit": "events",
++ "window": "hour",
++ "window_start": "2026-09-26T09:00:00.000Z",
++ "window_end": "2026-09-26T10:00:00.000Z",
++ "quantity": 1800,
++ "errors": 61,
++ "error_codes": {
++ "events.quota_exceeded": 60,
++ "events.type_not_allowed": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T09:59:58.120Z",
++ "code": "events.quota_exceeded",
++ "status": 429,
++ "trace_id": "0af7651916cd43dd8448eb211c80319c"
++ }
++ ]
++}
ddiff --git a/fixtures/events.usage.recorded/valid/deliveries.json b/fixtures/events.usage.recorded/valid/deliveries.json
nnew file mode 100644
iindex 00000000..73ed3d4a
---- /dev/null
++++ b/fixtures/events.usage.recorded/valid/deliveries.json
@@@ -0,0 +1,30 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "events.app.subscribe",
++ "unit": "deliveries",
++ "window": "hour",
++ "window_start": "2026-09-26T10:00:00.000Z",
++ "window_end": "2026-09-26T11:00:00.000Z",
++ "quantity": 12,
++ "errors": 2,
++ "error_codes": {
++ "events.delivery.http_500": 1,
++ "events.delivery.timeout": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T10:31:00.000Z",
++ "code": "events.delivery.timeout",
++ "trace_id": "0af7651916cd43dd8448eb211c80319c",
++ "ref": "evt_01JAB2C3D4E5F6G7H8J9K0MNPT"
++ },
++ {
++ "at": "2026-09-26T10:30:00.000Z",
++ "code": "events.delivery.http_500",
++ "status": 500,
++ "trace_id": "0af7651916cd43dd8448eb211c80319c",
++ "ref": "evt_01JAB2C3D4E5F6G7H8J9K0MNPT"
++ }
++ ]
++}
ddiff --git a/fixtures/events.usage.recorded/valid/publish-refused.json b/fixtures/events.usage.recorded/valid/publish-refused.json
nnew file mode 100644
iindex 00000000..9d55e272
---- /dev/null
++++ b/fixtures/events.usage.recorded/valid/publish-refused.json
@@@ -0,0 +1,23 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "sandbox",
++ "capability": "events.app.publish",
++ "unit": "events",
++ "window": "hour",
++ "window_start": "2026-09-26T09:00:00.000Z",
++ "window_end": "2026-09-26T10:00:00.000Z",
++ "quantity": 1800,
++ "errors": 61,
++ "error_codes": {
++ "events.quota_exceeded": 60,
++ "events.type_not_allowed": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T09:59:58.120Z",
++ "code": "events.quota_exceeded",
++ "status": 429,
++ "trace_id": "0af7651916cd43dd8448eb211c80319c"
++ }
++ ]
++}
ddiff --git a/fixtures/network.project-usage-result/invalid/a-year.json b/fixtures/network.project-usage-result/invalid/a-year.json
nnew file mode 100644
iindex 00000000..161bbefc
---- /dev/null
++++ b/fixtures/network.project-usage-result/invalid/a-year.json
@@@ -0,0 +1,115 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "all",
++ "range": {
++ "days": 365,
++ "from": "2026-08-28",
++ "to": "2026-09-26"
++ },
++ "generated_at": "2026-09-26T15:05:00.000Z",
++ "last_recorded_at": "2026-09-26T15:02:11.000Z",
++ "freshness": "Usage arrives as hourly rollups after each hour closes; the current hour is not counted yet.",
++ "totals": [
++ {
++ "service": "tools",
++ "capability": "tools.job.create",
++ "unit": "jobs",
++ "env": "production",
++ "quantity": 42,
++ "errors": 3
++ },
++ {
++ "service": "events",
++ "capability": "events.app.publish",
++ "unit": "events",
++ "env": "sandbox",
++ "quantity": 1800,
++ "errors": 61
++ }
++ ],
++ "daily": [
++ {
++ "day": "2026-09-26",
++ "service": "tools",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "env": "production",
++ "quantity": 42,
++ "errors": 3
++ },
++ {
++ "day": "2026-09-26",
++ "service": "events",
++ "capability": "events.app.publish",
++ "dimension": null,
++ "unit": "events",
++ "env": "sandbox",
++ "quantity": 1800,
++ "errors": 61
++ }
++ ],
++ "quotas": [
++ {
++ "capability": "tools.job.create",
++ "limit": 1000,
++ "window": "day",
++ "unit": "jobs",
++ "enforced_by": "openvibe.tools",
++ "used": 42,
++ "remaining": 958,
++ "window_start": "2026-09-26T00:00:00.000Z",
++ "note": null
++ },
++ {
++ "capability": "events.app.publish",
++ "limit": 120,
++ "window": "minute",
++ "unit": "events",
++ "enforced_by": "openvibe.events",
++ "used": null,
++ "remaining": null,
++ "window_start": null,
++ "note": "a minute window is enforced by the service as it happens; hourly rollups cannot show it"
++ }
++ ],
++ "errors": {
++ "total": 64,
++ "by_code": [
++ {
++ "service": "events",
++ "capability": "events.app.publish",
++ "code": "events.quota_exceeded",
++ "count": 60
++ },
++ {
++ "service": "tools",
++ "capability": "tools.job.create",
++ "code": "tools.job.failed",
++ "count": 2
++ }
++ ],
++ "recent": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "env": "production",
++ "service": "tools",
++ "capability": "tools.job.create",
++ "code": "tools.job.timeout",
++ "status": 504,
++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ {
++ "at": "2026-09-26T09:59:58.120Z",
++ "env": "sandbox",
++ "service": "events",
++ "capability": "events.app.publish",
++ "code": "events.quota_exceeded",
++ "status": 429,
++ "trace_id": "0af7651916cd43dd8448eb211c80319c",
++ "ref": null
++ }
++ ]
++ }
++}
ddiff --git a/fixtures/network.project-usage-result/invalid/negative-use.json b/fixtures/network.project-usage-result/invalid/negative-use.json
nnew file mode 100644
iindex 00000000..14c6c455
---- /dev/null
++++ b/fixtures/network.project-usage-result/invalid/negative-use.json
@@@ -0,0 +1,115 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "all",
++ "range": {
++ "days": 30,
++ "from": "2026-08-28",
++ "to": "2026-09-26"
++ },
++ "generated_at": "2026-09-26T15:05:00.000Z",
++ "last_recorded_at": "2026-09-26T15:02:11.000Z",
++ "freshness": "Usage arrives as hourly rollups after each hour closes; the current hour is not counted yet.",
++ "totals": [
++ {
++ "service": "tools",
++ "capability": "tools.job.create",
++ "unit": "jobs",
++ "env": "production",
++ "quantity": 42,
++ "errors": 3
++ },
++ {
++ "service": "events",
++ "capability": "events.app.publish",
++ "unit": "events",
++ "env": "sandbox",
++ "quantity": 1800,
++ "errors": 61
++ }
++ ],
++ "daily": [
++ {
++ "day": "2026-09-26",
++ "service": "tools",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "env": "production",
++ "quantity": 42,
++ "errors": 3
++ },
++ {
++ "day": "2026-09-26",
++ "service": "events",
++ "capability": "events.app.publish",
++ "dimension": null,
++ "unit": "events",
++ "env": "sandbox",
++ "quantity": 1800,
++ "errors": 61
++ }
++ ],
++ "quotas": [
++ {
++ "capability": "tools.job.create",
++ "limit": 1000,
++ "window": "day",
++ "unit": "jobs",
++ "enforced_by": "openvibe.tools",
++ "used": -5,
++ "remaining": 958,
++ "window_start": "2026-09-26T00:00:00.000Z",
++ "note": null
++ },
++ {
++ "capability": "events.app.publish",
++ "limit": 120,
++ "window": "minute",
++ "unit": "events",
++ "enforced_by": "openvibe.events",
++ "used": null,
++ "remaining": null,
++ "window_start": null,
++ "note": "a minute window is enforced by the service as it happens; hourly rollups cannot show it"
++ }
++ ],
++ "errors": {
++ "total": 64,
++ "by_code": [
++ {
++ "service": "events",
++ "capability": "events.app.publish",
++ "code": "events.quota_exceeded",
++ "count": 60
++ },
++ {
++ "service": "tools",
++ "capability": "tools.job.create",
++ "code": "tools.job.failed",
++ "count": 2
++ }
++ ],
++ "recent": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "env": "production",
++ "service": "tools",
++ "capability": "tools.job.create",
++ "code": "tools.job.timeout",
++ "status": 504,
++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ {
++ "at": "2026-09-26T09:59:58.120Z",
++ "env": "sandbox",
++ "service": "events",
++ "capability": "events.app.publish",
++ "code": "events.quota_exceeded",
++ "status": 429,
++ "trace_id": "0af7651916cd43dd8448eb211c80319c",
++ "ref": null
++ }
++ ]
++ }
++}
ddiff --git a/fixtures/network.project-usage-result/invalid/recent-error-names-a-person.json b/fixtures/network.project-usage-result/invalid/recent-error-names-a-person.json
nnew file mode 100644
iindex 00000000..7a17dc12
---- /dev/null
++++ b/fixtures/network.project-usage-result/invalid/recent-error-names-a-person.json
@@@ -0,0 +1,119 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "all",
++ "range": {
++ "days": 30,
++ "from": "2026-08-28",
++ "to": "2026-09-26"
++ },
++ "generated_at": "2026-09-26T15:05:00.000Z",
++ "last_recorded_at": "2026-09-26T15:02:11.000Z",
++ "freshness": "Usage arrives as hourly rollups after each hour closes; the current hour is not counted yet.",
++ "totals": [
++ {
++ "service": "tools",
++ "capability": "tools.job.create",
++ "unit": "jobs",
++ "env": "production",
++ "quantity": 42,
++ "errors": 3
++ },
++ {
++ "service": "events",
++ "capability": "events.app.publish",
++ "unit": "events",
++ "env": "sandbox",
++ "quantity": 1800,
++ "errors": 61
++ }
++ ],
++ "daily": [
++ {
++ "day": "2026-09-26",
++ "service": "tools",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "env": "production",
++ "quantity": 42,
++ "errors": 3
++ },
++ {
++ "day": "2026-09-26",
++ "service": "events",
++ "capability": "events.app.publish",
++ "dimension": null,
++ "unit": "events",
++ "env": "sandbox",
++ "quantity": 1800,
++ "errors": 61
++ }
++ ],
++ "quotas": [
++ {
++ "capability": "tools.job.create",
++ "limit": 1000,
++ "window": "day",
++ "unit": "jobs",
++ "enforced_by": "openvibe.tools",
++ "used": 42,
++ "remaining": 958,
++ "window_start": "2026-09-26T00:00:00.000Z",
++ "note": null
++ },
++ {
++ "capability": "events.app.publish",
++ "limit": 120,
++ "window": "minute",
++ "unit": "events",
++ "enforced_by": "openvibe.events",
++ "used": null,
++ "remaining": null,
++ "window_start": null,
++ "note": "a minute window is enforced by the service as it happens; hourly rollups cannot show it"
++ }
++ ],
++ "errors": {
++ "total": 64,
++ "by_code": [
++ {
++ "service": "events",
++ "capability": "events.app.publish",
++ "code": "events.quota_exceeded",
++ "count": 60
++ },
++ {
++ "service": "tools",
++ "capability": "tools.job.create",
++ "code": "tools.job.failed",
++ "count": 2
++ }
++ ],
++ "recent": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "env": "production",
++ "service": "tools",
++ "capability": "tools.job.create",
++ "code": "tools.job.timeout",
++ "status": 504,
++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "subject": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ }
++ },
++ {
++ "at": "2026-09-26T09:59:58.120Z",
++ "env": "sandbox",
++ "service": "events",
++ "capability": "events.app.publish",
++ "code": "events.quota_exceeded",
++ "status": 429,
++ "trace_id": "0af7651916cd43dd8448eb211c80319c",
++ "ref": null
++ }
++ ]
++ }
++}
ddiff --git a/fixtures/network.project-usage-result/valid/dashboard.json b/fixtures/network.project-usage-result/valid/dashboard.json
nnew file mode 100644
iindex 00000000..8d5d9769
---- /dev/null
++++ b/fixtures/network.project-usage-result/valid/dashboard.json
@@@ -0,0 +1,115 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "all",
++ "range": {
++ "days": 30,
++ "from": "2026-08-28",
++ "to": "2026-09-26"
++ },
++ "generated_at": "2026-09-26T15:05:00.000Z",
++ "last_recorded_at": "2026-09-26T15:02:11.000Z",
++ "freshness": "Usage arrives as hourly rollups after each hour closes; the current hour is not counted yet.",
++ "totals": [
++ {
++ "service": "tools",
++ "capability": "tools.job.create",
++ "unit": "jobs",
++ "env": "production",
++ "quantity": 42,
++ "errors": 3
++ },
++ {
++ "service": "events",
++ "capability": "events.app.publish",
++ "unit": "events",
++ "env": "sandbox",
++ "quantity": 1800,
++ "errors": 61
++ }
++ ],
++ "daily": [
++ {
++ "day": "2026-09-26",
++ "service": "tools",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "env": "production",
++ "quantity": 42,
++ "errors": 3
++ },
++ {
++ "day": "2026-09-26",
++ "service": "events",
++ "capability": "events.app.publish",
++ "dimension": null,
++ "unit": "events",
++ "env": "sandbox",
++ "quantity": 1800,
++ "errors": 61
++ }
++ ],
++ "quotas": [
++ {
++ "capability": "tools.job.create",
++ "limit": 1000,
++ "window": "day",
++ "unit": "jobs",
++ "enforced_by": "openvibe.tools",
++ "used": 42,
++ "remaining": 958,
++ "window_start": "2026-09-26T00:00:00.000Z",
++ "note": null
++ },
++ {
++ "capability": "events.app.publish",
++ "limit": 120,
++ "window": "minute",
++ "unit": "events",
++ "enforced_by": "openvibe.events",
++ "used": null,
++ "remaining": null,
++ "window_start": null,
++ "note": "a minute window is enforced by the service as it happens; hourly rollups cannot show it"
++ }
++ ],
++ "errors": {
++ "total": 64,
++ "by_code": [
++ {
++ "service": "events",
++ "capability": "events.app.publish",
++ "code": "events.quota_exceeded",
++ "count": 60
++ },
++ {
++ "service": "tools",
++ "capability": "tools.job.create",
++ "code": "tools.job.failed",
++ "count": 2
++ }
++ ],
++ "recent": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "env": "production",
++ "service": "tools",
++ "capability": "tools.job.create",
++ "code": "tools.job.timeout",
++ "status": 504,
++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ {
++ "at": "2026-09-26T09:59:58.120Z",
++ "env": "sandbox",
++ "service": "events",
++ "capability": "events.app.publish",
++ "code": "events.quota_exceeded",
++ "status": 429,
++ "trace_id": "0af7651916cd43dd8448eb211c80319c",
++ "ref": null
++ }
++ ]
++ }
++}
ddiff --git a/fixtures/network.project-usage-result/valid/nothing-yet.json b/fixtures/network.project-usage-result/valid/nothing-yet.json
nnew file mode 100644
iindex 00000000..6738f2b0
---- /dev/null
++++ b/fixtures/network.project-usage-result/valid/nothing-yet.json
@@@ -0,0 +1,20 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "range": {
++ "days": 7,
++ "from": "2026-09-20",
++ "to": "2026-09-26"
++ },
++ "generated_at": "2026-09-26T15:05:00.000Z",
++ "last_recorded_at": null,
++ "freshness": "No usage has been recorded for this project yet.",
++ "totals": [],
++ "daily": [],
++ "quotas": [],
++ "errors": {
++ "total": 0,
++ "by_code": [],
++ "recent": []
++ }
++}
ddiff --git a/fixtures/tools.usage.recorded/invalid/job-owner.json b/fixtures/tools.usage.recorded/invalid/job-owner.json
nnew file mode 100644
iindex 00000000..ddb2fc54
---- /dev/null
++++ b/fixtures/tools.usage.recorded/invalid/job-owner.json
@@@ -0,0 +1,35 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "window": "hour",
++ "window_start": "2026-09-26T14:00:00.000Z",
++ "window_end": "2026-09-26T15:00:00.000Z",
++ "quantity": 42,
++ "errors": 3,
++ "error_codes": {
++ "tools.job.failed": 2,
++ "tools.job.timeout": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "code": "tools.job.timeout",
++ "status": 504,
++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ {
++ "at": "2026-09-26T14:20:13.001Z",
++ "code": "tools.job.failed",
++ "status": 422,
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ }
++ ],
++ "owner": {
++ "type": "app",
++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ }
++}
ddiff --git a/fixtures/tools.usage.recorded/invalid/no-project.json b/fixtures/tools.usage.recorded/invalid/no-project.json
nnew file mode 100644
iindex 00000000..655426e3
---- /dev/null
++++ b/fixtures/tools.usage.recorded/invalid/no-project.json
@@@ -0,0 +1,30 @@
++{
++ "env": "production",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "window": "hour",
++ "window_start": "2026-09-26T14:00:00.000Z",
++ "window_end": "2026-09-26T15:00:00.000Z",
++ "quantity": 42,
++ "errors": 3,
++ "error_codes": {
++ "tools.job.failed": 2,
++ "tools.job.timeout": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "code": "tools.job.timeout",
++ "status": 504,
++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ {
++ "at": "2026-09-26T14:20:13.001Z",
++ "code": "tools.job.failed",
++ "status": 422,
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ }
++ ]
++}
ddiff --git a/fixtures/tools.usage.recorded/valid/img-process-hour.json b/fixtures/tools.usage.recorded/valid/img-process-hour.json
nnew file mode 100644
iindex 00000000..df4f4a70
---- /dev/null
++++ b/fixtures/tools.usage.recorded/valid/img-process-hour.json
@@@ -0,0 +1,31 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "capability": "tools.job.create",
++ "dimension": "img.process",
++ "unit": "jobs",
++ "window": "hour",
++ "window_start": "2026-09-26T14:00:00.000Z",
++ "window_end": "2026-09-26T15:00:00.000Z",
++ "quantity": 42,
++ "errors": 3,
++ "error_codes": {
++ "tools.job.failed": 2,
++ "tools.job.timeout": 1
++ },
++ "samples": [
++ {
++ "at": "2026-09-26T14:51:07.412Z",
++ "code": "tools.job.timeout",
++ "status": 504,
++ "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ {
++ "at": "2026-09-26T14:20:13.001Z",
++ "code": "tools.job.failed",
++ "status": 422,
++ "ref": "job_01JAB2C3D4E5F6G7H8J9K0MNPS"
++ }
++ ]
++}
ddiff --git a/fixtures/tools.usage.recorded/valid/tool-run-sandbox.json b/fixtures/tools.usage.recorded/valid/tool-run-sandbox.json
nnew file mode 100644
iindex 00000000..5e1be975
---- /dev/null
++++ b/fixtures/tools.usage.recorded/valid/tool-run-sandbox.json
@@@ -0,0 +1,12 @@
++{
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "sandbox",
++ "capability": "tools.tool.run",
++ "unit": "jobs",
++ "window": "hour",
++ "window_start": "2026-09-26T08:00:00.000Z",
++ "window_end": "2026-09-26T09:00:00.000Z",
++ "quantity": 5,
++ "errors": 0,
++ "dimension": "image-resize"
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 54bb6e74..e669a96f 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -2580,6 +2580,150 @@
}
}
},
++ "common.usage-recorded": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/common/usage-recorded.v1.json",
++ "title": "UsageRecorded",
++ "description": "common.usage-recorded@1 (roadmap WS-N task 4, ADR-014): one rollup of a developer project's use of one capability in one environment over one closed window (an hour or a day), as the service that owns the capability counted it. The payload of every <service>.usage.recorded event; OpenVibe.Network adds them up per project and day for the project's dashboard on OpenVibe.Codes. The producer counts in the transaction that does its own accounting (a job's end, a stored event, a delivery attempt) and writes the rollup to its outbox after the window has closed: never one event per request. Totals, not deltas: a later event with the same key (source, project_id, env, capability, dimension, unit, window_start) replaces the earlier one, and a lower revision never replaces a higher one. Envelope: subject { type: project, id: <project_id> }, visibility internal, priority low, actor the producing service. Never carries a subject id, an address, a session, a request's input or content, or a file name.",
++ "type": "object",
++ "required": [
++ "project_id",
++ "env",
++ "capability",
++ "unit",
++ "window",
++ "window_start",
++ "window_end",
++ "quantity",
++ "errors"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The developer project whose app token did the work."
++ },
++ "env": {
++ "enum": [
++ "sandbox",
++ "production"
++ ],
++ "description": "The environment of that app token."
++ },
++ "capability": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$",
++ "maxLength": 80,
++ "description": "The capability the usage counts against: tools.job.create, tools.tool.run, events.app.publish, events.app.subscribe."
++ },
++ "dimension": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 80,
++ "pattern": "^[a-z0-9][a-z0-9_.:-]*$",
++ "not": {
++ "pattern": "^(usr|gst|app|mod|svc|job|evt|req)_"
++ },
++ "description": "Optional finer key inside the capability, as the producer names it: a job type (img.process) or a tool id (image-resize). Never the id of a person, an app, a request or a job."
++ },
++ "unit": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$",
++ "description": "What quantity counts (jobs, events, deliveries, requests, bytes, tokens): the same word a Network project quota uses for its unit."
++ },
++ "window": {
++ "enum": [
++ "hour",
++ "day"
++ ],
++ "description": "The length of the window."
++ },
++ "window_start": {
++ "type": "string",
++ "format": "date-time",
++ "description": "UTC, on the hour (midnight for a day)."
++ },
++ "window_end": {
++ "type": "string",
++ "format": "date-time",
++ "description": "window_start plus the window, exclusive."
++ },
++ "quantity": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "Units used in the window, by work that succeeded and work that failed alike."
++ },
++ "errors": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "Operations in the window that failed or were refused: a job that failed, a publish refused with a problem, a webhook attempt without a 2xx. A refused operation may use no unit, so errors can exceed quantity."
++ },
++ "error_codes": {
++ "type": "object",
++ "maxProperties": 20,
++ "propertyNames": {
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$",
++ "maxLength": 80
++ },
++ "additionalProperties": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "description": "The errors by problem code (at most 20 codes; the rest are only in errors)."
++ },
++ "samples": {
++ "type": "array",
++ "maxItems": 10,
++ "description": "The last failures of the window, newest first, so a developer can find them in their own logs.",
++ "items": {
++ "type": "object",
++ "required": [
++ "at",
++ "code"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$",
++ "maxLength": 80
++ },
++ "status": {
++ "type": "integer",
++ "minimum": 100,
++ "maximum": 599,
++ "description": "The HTTP status the failure was answered with, when there was one."
++ },
++ "trace_id": {
++ "type": "string",
++ "pattern": "^[0-9a-f]{32}$",
++ "description": "The W3C trace id of the request (or event) that failed."
++ },
++ "ref": {
++ "type": "string",
++ "pattern": "^(job|evt|run|med)_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject."
++ }
++ }
++ }
++ },
++ "revision": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "1 when absent. A producer that re-emits a window with corrected totals raises it."
++ }
++ }
++ }
++ },
"media.file-upload": {
"version": "1.0.0",
"owner": "media",
@@@ -23671,6 +23815,30 @@
"$ref": "../../common/moderation-action.v1.json"
}
},
++ "tools.usage.recorded": {
++ "version": "1.0.0",
++ "owner": "tools",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/tools.usage.recorded.v1.json",
++ "title": "ToolsUsageRecordedPayload",
++ "description": "tools.usage.recorded v1 (roadmap WS-N task 4, OpenVibe.Tools apps/_shared/jobs/usage.js). One hour of a developer project's jobs on one Tools satellite, per capability (tools.job.create for POST /api/v1/jobs, tools.tool.run for a tool run) and job type or tool (dimension), in unit jobs: quantity counts the jobs that ended (succeeded or failed; a cancelled job is not counted), errors the failed ones, by code, with the last failures' job ids and trace ids. Counted in the transaction that records a job's end; written to the satellite's outbox after the hour closes. Only jobs of developer-app tokens (they carry a project) are counted; sandbox jobs are counted under env sandbox. The payload is common.usage-recorded@1. Envelope: subject { type: project, id: <project_id> }, visibility internal, priority low, actor service:tools.",
++ "$ref": "../../common/usage-recorded.v1.json"
++ }
++ },
++ "events.usage.recorded": {
++ "version": "1.0.0",
++ "owner": "events",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/events.usage.recorded.v1.json",
++ "title": "EventsUsageRecordedPayload",
++ "description": "events.usage.recorded v1 (roadmap WS-N task 4, OpenVibe.Events server/usage.js). One hour of a developer project's use of OpenVibe.Events in one environment: events.app.publish in unit events (quantity counts the events stored; errors the publish requests refused with a problem, such as 429 events.quota_exceeded or 403 events.type_not_allowed) and events.app.subscribe in unit deliveries (quantity counts webhook delivery attempts to the project's subscriptions; errors the attempts without a 2xx, as events.delivery.http_<status>, events.delivery.timeout or events.delivery.failed). Counted in the transaction that stores the events or records the attempt (a refusal right after it is answered); Events stores the rollup as its own event after the hour closes, in the same transaction that marks it sent. The payload is common.usage-recorded@1. Envelope: source events, subject { type: project, id: <project_id> }, visibility internal, priority low, actor service:events.",
++ "$ref": "../../common/usage-recorded.v1.json"
++ }
++ },
"live.stream.started": {
"version": "1.0.0",
"owner": "live",
@@@ -26194,6 +26362,379 @@
}
}
},
++ "network.project-usage-result": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/network/project-usage-result.v1.json",
++ "title": "ProjectUsageResult",
++ "description": "network.project-usage-result@1 (roadmap WS-N task 4, ADR-014): what GET /api/v1/projects/:project/usage?days=&env= on OpenVibe.Network answers a project's owner or admin (or staff): the project's usage per day, service, capability and unit, added up from the <service>.usage.recorded rollups (common.usage-recorded@1) the owning services emit after each hour closes; the project's recorded quotas with what the current window has used; and its recent failures with their codes and trace ids. Counts only, never who did what: no subject id, address, input or content.",
++ "type": "object",
++ "required": [
++ "project_id",
++ "env",
++ "range",
++ "generated_at",
++ "last_recorded_at",
++ "freshness",
++ "totals",
++ "daily",
++ "quotas",
++ "errors"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{1,39}$"
++ },
++ "capability": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+){2,}$",
++ "maxLength": 80
++ },
++ "unit": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]{0,31}$"
++ },
++ "env": {
++ "enum": [
++ "sandbox",
++ "production"
++ ]
++ },
++ "code": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$",
++ "maxLength": 80
++ },
++ "count": {
++ "type": "integer",
++ "minimum": 0
++ }
++ },
++ "properties": {
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "env": {
++ "enum": [
++ "all",
++ "sandbox",
++ "production"
++ ],
++ "description": "The environments counted (the env query parameter; all by default)."
++ },
++ "range": {
++ "type": "object",
++ "required": [
++ "days",
++ "from",
++ "to"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "days": {
++ "type": "integer",
++ "minimum": 1,
++ "maximum": 90
++ },
++ "from": {
++ "type": "string",
++ "format": "date",
++ "description": "First UTC day counted."
++ },
++ "to": {
++ "type": "string",
++ "format": "date",
++ "description": "Last UTC day counted (today)."
++ }
++ }
++ },
++ "generated_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "last_recorded_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "When the newest rollup for this project arrived; null before the first."
++ },
++ "freshness": {
++ "type": "string",
++ "maxLength": 300,
++ "description": "How current the numbers are, in words (rollups arrive after each hour closes)."
++ },
++ "totals": {
++ "type": "array",
++ "description": "The whole range per service, capability, unit and environment.",
++ "items": {
++ "type": "object",
++ "required": [
++ "service",
++ "capability",
++ "unit",
++ "env",
++ "quantity",
++ "errors"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "$ref": "#/$defs/service"
++ },
++ "capability": {
++ "$ref": "#/$defs/capability"
++ },
++ "unit": {
++ "$ref": "#/$defs/unit"
++ },
++ "env": {
++ "$ref": "#/$defs/env"
++ },
++ "quantity": {
++ "$ref": "#/$defs/count"
++ },
++ "errors": {
++ "$ref": "#/$defs/count"
++ }
++ }
++ }
++ },
++ "daily": {
++ "type": "array",
++ "description": "One row per UTC day, service, capability, dimension, unit and environment that saw usage; days without any are absent. Newest day first.",
++ "items": {
++ "type": "object",
++ "required": [
++ "day",
++ "service",
++ "capability",
++ "dimension",
++ "unit",
++ "env",
++ "quantity",
++ "errors"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "day": {
++ "type": "string",
++ "format": "date"
++ },
++ "service": {
++ "$ref": "#/$defs/service"
++ },
++ "capability": {
++ "$ref": "#/$defs/capability"
++ },
++ "dimension": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 80
++ },
++ "unit": {
++ "$ref": "#/$defs/unit"
++ },
++ "env": {
++ "$ref": "#/$defs/env"
++ },
++ "quantity": {
++ "$ref": "#/$defs/count"
++ },
++ "errors": {
++ "$ref": "#/$defs/count"
++ }
++ }
++ }
++ },
++ "quotas": {
++ "type": "array",
++ "description": "The project's recorded quotas (GET /api/v1/projects/:project/quotas), each with what its current window has used, in every environment together. used is null where the rollups cannot tell (a minute or hour window, or a unit no service reports for that capability).",
++ "items": {
++ "type": "object",
++ "required": [
++ "capability",
++ "limit",
++ "window",
++ "unit",
++ "enforced_by",
++ "used",
++ "remaining",
++ "window_start",
++ "note"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "capability": {
++ "$ref": "#/$defs/capability"
++ },
++ "limit": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "window": {
++ "enum": [
++ "minute",
++ "hour",
++ "day",
++ "month",
++ "total"
++ ]
++ },
++ "unit": {
++ "$ref": "#/$defs/unit"
++ },
++ "enforced_by": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "The audience of the service that enforces it (openvibe.<owner>)."
++ },
++ "used": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "remaining": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 0
++ },
++ "window_start": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time",
++ "description": "Start of the current window counted (today, this month); null for total and when used is null."
++ },
++ "note": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 300
++ }
++ }
++ }
++ },
++ "errors": {
++ "type": "object",
++ "required": [
++ "total",
++ "by_code",
++ "recent"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "total": {
++ "$ref": "#/$defs/count"
++ },
++ "by_code": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "service",
++ "capability",
++ "code",
++ "count"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "service": {
++ "$ref": "#/$defs/service"
++ },
++ "capability": {
++ "$ref": "#/$defs/capability"
++ },
++ "code": {
++ "$ref": "#/$defs/code"
++ },
++ "count": {
++ "$ref": "#/$defs/count"
++ }
++ }
++ }
++ },
++ "recent": {
++ "type": "array",
++ "maxItems": 50,
++ "description": "The newest failures the rollups sampled, newest first.",
++ "items": {
++ "type": "object",
++ "required": [
++ "at",
++ "env",
++ "service",
++ "capability",
++ "code",
++ "status",
++ "trace_id",
++ "ref"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "env": {
++ "$ref": "#/$defs/env"
++ },
++ "service": {
++ "$ref": "#/$defs/service"
++ },
++ "capability": {
++ "$ref": "#/$defs/capability"
++ },
++ "code": {
++ "$ref": "#/$defs/code"
++ },
++ "status": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "minimum": 100,
++ "maximum": 599
++ },
++ "trace_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^[0-9a-f]{32}$"
++ },
++ "ref": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^(job|evt|run|med)_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ },
"network.staff-list-result": {
"version": "1.0.0",
"owner": "network",
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 6ac8bb49..4b0e8d74 100644
---- a/generated/openapi/ai.json
++++ b/generated/openapi/ai.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.AI API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex 990a5c47..b4335059 100644
---- a/generated/openapi/billing.json
++++ b/generated/openapi/billing.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Billing API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex 89fd99eb..7968730e 100644
---- a/generated/openapi/blog.json
++++ b/generated/openapi/blog.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Blog API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/chat.json b/generated/openapi/chat.json
iindex 7303139b..22344b1c 100644
---- a/generated/openapi/chat.json
++++ b/generated/openapi/chat.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Chat API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/codes.json b/generated/openapi/codes.json
iindex b3f8663e..2630f1d0 100644
---- a/generated/openapi/codes.json
++++ b/generated/openapi/codes.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Codes API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex d37d5306..fd4f5aee 100644
---- a/generated/openapi/community.json
++++ b/generated/openapi/community.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Community API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex c73a0792..7d642621 100644
---- a/generated/openapi/coupons.json
++++ b/generated/openapi/coupons.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Coupons API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/deals.json b/generated/openapi/deals.json
iindex f60d3b27..a3ab53c9 100644
---- a/generated/openapi/deals.json
++++ b/generated/openapi/deals.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Deals API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex 934bfee6..e121472a 100644
---- a/generated/openapi/events.json
++++ b/generated/openapi/events.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Events API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex ff2ba018..a16a27e0 100644
---- a/generated/openapi/games.json
++++ b/generated/openapi/games.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Games API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/host.json b/generated/openapi/host.json
iindex fab3b870..d5e4b191 100644
---- a/generated/openapi/host.json
++++ b/generated/openapi/host.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Host API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex 6caf719f..d3bf1f7d 100644
---- a/generated/openapi/live.json
++++ b/generated/openapi/live.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Live API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/media.json b/generated/openapi/media.json
iindex fcf6903e..5ebe0be2 100644
---- a/generated/openapi/media.json
++++ b/generated/openapi/media.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Media API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex b286281e..8a315113 100644
---- a/generated/openapi/network.json
++++ b/generated/openapi/network.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Network API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex eaa13be9..e8e85efc 100644
---- a/generated/openapi/news.json
++++ b/generated/openapi/news.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.News API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex 89c57796..cf3467fd 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRe.Stream API",
-- "version": "0.62.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex 3ad22156..b9927451 100644
---- a/generated/openapi/reviews.json
++++ b/generated/openapi/reviews.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Reviews API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex ca3c2f5f..d39817d9 100644
---- a/generated/openapi/search.json
++++ b/generated/openapi/search.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Search API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex 79e74156..3bd2edb7 100644
---- a/generated/openapi/sources.json
++++ b/generated/openapi/sources.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Sources API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex 81825a00..e7a125a4 100644
---- a/generated/openapi/tips.json
++++ b/generated/openapi/tips.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tips API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/tools.json b/generated/openapi/tools.json
iindex 86291b42..31983337 100644
---- a/generated/openapi/tools.json
++++ b/generated/openapi/tools.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tools API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/trade.json b/generated/openapi/trade.json
iindex ba4c70cf..77d15800 100644
---- a/generated/openapi/trade.json
++++ b/generated/openapi/trade.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Trade API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/vip.json b/generated/openapi/vip.json
iindex a258165c..febe3027 100644
---- a/generated/openapi/vip.json
++++ b/generated/openapi/vip.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.VIP API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex bbc50591..7517d4ae 100644
---- a/generated/openapi/wiki.json
++++ b/generated/openapi/wiki.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Wiki API",
-- "version": "0.62.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.62.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.63.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.63.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"
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 7d0ec0a1..65903c7f 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -1176,1286 +1176,2250 @@ export interface ModerationAction {
details?: {};
}
--/** [email protected] (owner: media) */
--/**
-- * media.file-upload@1: the multipart/form-data body of POST /api/v1/:app/files (media.object.upload, namespace :app): one part named `file` with the bytes and its filename and type. The tenant's quota is checked against the bytes; the answer is media.file@1 (201).
-- */
--export interface MediaFileUpload {
-- /**
-- * The multipart part named file (filename and Content-Type from the part).
-- */
-- file: string;
-- [k: string]: unknown | undefined;
--}
--
--/** [email protected] (owner: search) */
++/** [email protected] (owner: network) */
/**
-- * What an owning service tells OpenVibe.Search about one of its resources (roadmap §15.12, §4.2 A). The owner stays the source of truth; Search keeps a revision-ordered, permission-aware copy for discovery. A document with deleted=true is a tombstone: it removes the resource from every result and wins over any upsert with the same or an older revision.
++ * common.usage-recorded@1 (roadmap WS-N task 4, ADR-014): one rollup of a developer project's use of one capability in one environment over one closed window (an hour or a day), as the service that owns the capability counted it. The payload of every <service>.usage.recorded event; OpenVibe.Network adds them up per project and day for the project's dashboard on OpenVibe.Codes. The producer counts in the transaction that does its own accounting (a job's end, a stored event, a delivery attempt) and writes the rollup to its outbox after the window has closed: never one event per request. Totals, not deltas: a later event with the same key (source, project_id, env, capability, dimension, unit, window_start) replaces the earlier one, and a lower revision never replaces a higher one. Envelope: subject { type: project, id: <project_id> }, visibility internal, priority low, actor the producing service. Never carries a subject id, an address, a session, a request's input or content, or a file name.
*/
--export interface IndexDocument {
-- /**
-- * Owning service id (wiki, blog, news, community, sources…). Only that service may write the document.
-- */
-- owner: string;
-- /**
-- * Resource type inside the owner (page, post, story, entity, offer, coupon, instrument, item…).
-- */
-- type: string;
++export interface UsageRecorded {
/**
-- * Resource id inside owner+type. Stable across revisions.
++ * The developer project whose app token did the work.
*/
-- id: string;
++ project_id: string;
/**
-- * Owner's monotonic revision of the resource. An older revision never overwrites a newer one; at equal revision a deletion wins.
++ * The environment of that app token.
*/
-- revision: number;
++ env: "sandbox" | "production";
/**
-- * Deletion marker. true = tombstone; every other content field is ignored.
++ * The capability the usage counts against: tools.job.create, tools.tool.run, events.app.publish, events.app.subscribe.
*/
-- deleted?: boolean;
++ capability: string;
/**
-- * public: anyone. unlisted: never listed except to ACL matches; fetchable by exact id by a signed-in subject. members: subjects, groups or entitlements in acl. private: subjects in acl.subjects only. draft: never served.
++ * Optional finer key inside the capability, as the producer names it: a job type (img.process) or a tool id (image-resize). Never the id of a person, an app, a request or a job.
*/
-- visibility?: "public" | "unlisted" | "members" | "private" | "draft";
++ dimension?: string;
/**
-- * Who may see a non-public document. Ignored for public documents. Empty means nobody (for members/private) or direct-id only (for unlisted).
++ * What quantity counts (jobs, events, deliveries, requests, bytes, tokens): the same word a Network project quota uses for its unit.
*/
-- acl?: {
-- /**
-- * @maxItems 200
-- */
-- subjects?: string[];
-- /**
-- * Opaque group keys owned by some service, e.g. role:admin (Network role), wiki.space:spc_…:member.
-- *
-- * @maxItems 100
-- */
-- groups?: string[];
-- /**
-- * Entitlement keys (VIP/Billing), e.g. vip.plan:pln_…. Only a first-party service that just resolved them may present them for a viewer.
-- *
-- * @maxItems 100
-- */
-- entitlements?: string[];
-- };
-- canonical_url?: string;
-- title?: string;
-- summary?: string;
++ unit: string;
/**
-- * Plain text for full-text search (no HTML). Keep it under the Events payload limit when publishing through Events.
++ * The length of the window.
*/
-- body?: string;
++ window: "hour" | "day";
/**
-- * Filterable values (category, tags, space, author subject, currency…). Filter with facet.<key>=<value>.
++ * UTC, on the hour (midnight for a day).
*/
-- facets?: {
-- [k: string]: (string | number | boolean | string[]) | undefined;
-- };
++ window_start: string;
/**
-- * BCP 47 tag.
++ * window_start plus the window, exclusive.
*/
-- language?: string;
-- authorship?: "human" | "ai_assisted" | "ai_generated" | "imported";
++ window_end: string;
/**
-- * Where the content came from: typed references to source records (Sources items, citations, Media objects).
-- *
-- * @maxItems 50
++ * Units used in the window, by work that succeeded and work that failed alike.
*/
-- provenance?: {
-- service: string;
-- type: string;
-- id: string;
-- revision?: number;
-- label?: string;
-- url?: string;
-- retrieved_at?: string;
-- /**
-- * true when the reference came from a stub provider; the document is then never indexable.
-- */
-- stub?: boolean;
-- }[];
++ quantity: number;
/**
-- * Only published documents are ever served.
++ * Operations in the window that failed or were refused: a job that failed, a publish refused with a problem, a webhook attempt without a 2xx. A refused operation may use no unit, so errors can exceed quantity.
*/
-- publication_state?: "draft" | "scheduled" | "published" | "unpublished" | "retracted" | "archived";
-- published_at?: string | null;
-- updated_at?: string | null;
++ errors: number;
/**
-- * The owner's deterministic indexability decision (publishing packages, roadmap §32.3). Search can only make it stricter.
++ * The errors by problem code (at most 20 codes; the rest are only in errors).
*/
-- indexability?: {
-- decision: "index" | "noindex";
-- /**
-- * Known reasons: draft, private, members_only, unlisted, not_published, deleted, thin_content, duplicate_without_canonical, unsupported_claims, unsourced, stub_provider, sensitive_unreviewed, ai_unreviewed, missing_canonical_url, third_party_content, owner_decision.
-- *
-- * @maxItems 20
-- */
-- reasons?:
-- | []
-- | [string]
-- | [string, string]
-- | [string, string, string]
-- | [string, string, string, string]
-- | [string, string, string, string, string]
-- | [string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
-- | [
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string
-- ]
-- | [
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string
-- ]
-- | [
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string
-- ]
-- | [
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string
-- ]
-- | [
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string
-- ]
-- | [
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string
-- ];
++ error_codes?: {
++ [k: string]: number | undefined;
};
--}
--
--/** [email protected] (owner: search) */
--/**
-- * search.query@1: the query string of GET /api/v1/search (and, with q/owner/type/limit only, GET /api/v1/suggest), capabilities search.query.run and search.query.delegate. Who sees what is decided by Search per hit: anonymous callers get public, published, indexable documents; a person also restricted documents naming them; a delegating service passes X-OV-Subject. Filters narrow; they never widen visibility.
-- */
--export interface SearchQuery {
-- /**
-- * Words to match (FTS5; empty lists by recency).
-- */
-- q?: string;
-- /**
-- * One owner service (live, community, wiki, …).
-- */
-- owner?: string;
-- /**
-- * One document type within the owner (channel, vod, thread, …).
-- */
-- type?: string;
-- /**
-- * BCP 47 language.
-- */
-- lang?: string;
/**
-- * Comma-separated facet keys to count over what the caller can see.
++ * The last failures of the window, newest first, so a developer can find them in their own logs.
++ *
++ * @maxItems 10
*/
-- facets?: string;
-- limit?: number | string;
-- /**
-- * next_cursor of the previous page.
-- */
-- cursor?: string;
-- /**
-- * A facet filter (facet.<key>=<value>); repeat the parameter for several values.
-- *
-- * This interface was referenced by `SearchQuery`'s JSON-Schema definition
-- * via the `patternProperty` "^facet\.[a-z][a-z0-9_]{0,39}$".
-- */
-- [k: string]:
-- | string
++ samples?:
| []
-- | [string]
-- | [string, string]
-- | [string, string, string]
-- | [string, string, string, string]
-- | [string, string, string, string, string]
-- | [string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string, string, string, string, string, string]
-- | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
| [
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
]
| [
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
]
| [
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
]
| [
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
]
| [
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
]
-- | [
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string,
-- string
-- ];
--}
--
--/** [email protected] (owner: search) */
--/**
-- * search.query-result@1: the answer of GET /api/v1/search — one page of hits the caller may see, a cursor for the next page, and facet counts when asked. Never a total count and never an ACL.
-- */
--export interface SearchQueryResult {
-- /**
-- * @maxItems 100
-- */
-- results: {
-- owner: string;
-- type: string;
-- id: string;
-- revision?: number;
-- visibility?: string;
-- title?: string | null;
-- summary?: string | null;
-- canonical_url?: string | null;
-- facets?: {} | null;
-- language?: string | null;
-- authorship?: string | null;
-- provenance?: unknown[] | {} | null;
-- published_at?: string | null;
-- updated_at?: string | null;
-- /**
-- * false for a noindex document the caller may still see.
-- */
-- indexable?: boolean;
-- /**
-- * Escaped text with <mark> around the matches; nothing else.
-- */
-- snippet_html?: string | null;
-- }[];
-- next_cursor: string | null;
-- facets?: {
-- [k: string]:
-- | {
-- value: string;
-- count: number;
-- }[]
-- | undefined;
-- };
--}
--
--/** [email protected] (owner: search) */
--/**
-- * search.write-result@1: the answer of PUT /api/v1/documents/:owner/:type/:id (search.document.write). applied: stored (or a tombstone recorded); unchanged: the same revision with the same content. A revision older than the stored one is a 409 problem search.stale_revision; the same revision with different content is 409 search.revision_conflict.
-- */
--export interface SearchWriteResult {
-- outcome: "applied" | "unchanged";
-- revision: number;
-- /**
-- * What Search held before this write (null: nothing).
-- */
-- stored_revision?: number | null;
-- deleted?: boolean;
--}
--
--/** [email protected] (owner: sources) */
--/**
-- * A registered source (roadmap §4.2 B, Wave 14): where it is fetched from and on what terms. Credentials are referenced by environment-variable NAME only. A source's existence is never permission to fetch: robots and terms are verified before it is enabled, and the fetcher re-checks robots.txt on every run.
-- */
--export interface Source {
-- key: string;
-- name: string;
-- type: "rss" | "atom" | "sitemap" | "jsonld" | "api" | "manual";
-- category: "news" | "blog" | "reviews" | "deals" | "coupons" | "trade";
-- homepage_url?: string | null;
-- /**
-- * @maxItems 20
-- */
-- endpoints:
-- | []
| [
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- }
-- ]
-- | [
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- }
-- ]
-- | [
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
}
]
| [
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- }
-- ]
-- | [
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
}
]
| [
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
}
]
| [
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- }
-- ]
-- | [
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- }
-- ]
-- | [
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
},
{
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- }
-- ]
-- | [
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
}
-- ]
-- | [
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
-- [k: string]: string | undefined;
-- };
-- },
-- {
-- url: string;
-- format?: "json" | "xml";
-- items_path?: string;
-- item_kind?: string;
-- fields?: {
-- [k: string]: string | undefined;
-- };
-- extra?: {
++ ];
++ /**
++ * 1 when absent. A producer that re-emits a window with corrected totals raises it.
++ */
++ revision?: number;
++}
++
++/** [email protected] (owner: media) */
++/**
++ * media.file-upload@1: the multipart/form-data body of POST /api/v1/:app/files (media.object.upload, namespace :app): one part named `file` with the bytes and its filename and type. The tenant's quota is checked against the bytes; the answer is media.file@1 (201).
++ */
++export interface MediaFileUpload {
++ /**
++ * The multipart part named file (filename and Content-Type from the part).
++ */
++ file: string;
++ [k: string]: unknown | undefined;
++}
++
++/** [email protected] (owner: search) */
++/**
++ * What an owning service tells OpenVibe.Search about one of its resources (roadmap §15.12, §4.2 A). The owner stays the source of truth; Search keeps a revision-ordered, permission-aware copy for discovery. A document with deleted=true is a tombstone: it removes the resource from every result and wins over any upsert with the same or an older revision.
++ */
++export interface IndexDocument {
++ /**
++ * Owning service id (wiki, blog, news, community, sources…). Only that service may write the document.
++ */
++ owner: string;
++ /**
++ * Resource type inside the owner (page, post, story, entity, offer, coupon, instrument, item…).
++ */
++ type: string;
++ /**
++ * Resource id inside owner+type. Stable across revisions.
++ */
++ id: string;
++ /**
++ * Owner's monotonic revision of the resource. An older revision never overwrites a newer one; at equal revision a deletion wins.
++ */
++ revision: number;
++ /**
++ * Deletion marker. true = tombstone; every other content field is ignored.
++ */
++ deleted?: boolean;
++ /**
++ * public: anyone. unlisted: never listed except to ACL matches; fetchable by exact id by a signed-in subject. members: subjects, groups or entitlements in acl. private: subjects in acl.subjects only. draft: never served.
++ */
++ visibility?: "public" | "unlisted" | "members" | "private" | "draft";
++ /**
++ * Who may see a non-public document. Ignored for public documents. Empty means nobody (for members/private) or direct-id only (for unlisted).
++ */
++ acl?: {
++ /**
++ * @maxItems 200
++ */
++ subjects?: string[];
++ /**
++ * Opaque group keys owned by some service, e.g. role:admin (Network role), wiki.space:spc_…:member.
++ *
++ * @maxItems 100
++ */
++ groups?: string[];
++ /**
++ * Entitlement keys (VIP/Billing), e.g. vip.plan:pln_…. Only a first-party service that just resolved them may present them for a viewer.
++ *
++ * @maxItems 100
++ */
++ entitlements?: string[];
++ };
++ canonical_url?: string;
++ title?: string;
++ summary?: string;
++ /**
++ * Plain text for full-text search (no HTML). Keep it under the Events payload limit when publishing through Events.
++ */
++ body?: string;
++ /**
++ * Filterable values (category, tags, space, author subject, currency…). Filter with facet.<key>=<value>.
++ */
++ facets?: {
++ [k: string]: (string | number | boolean | string[]) | undefined;
++ };
++ /**
++ * BCP 47 tag.
++ */
++ language?: string;
++ authorship?: "human" | "ai_assisted" | "ai_generated" | "imported";
++ /**
++ * Where the content came from: typed references to source records (Sources items, citations, Media objects).
++ *
++ * @maxItems 50
++ */
++ provenance?: {
++ service: string;
++ type: string;
++ id: string;
++ revision?: number;
++ label?: string;
++ url?: string;
++ retrieved_at?: string;
++ /**
++ * true when the reference came from a stub provider; the document is then never indexable.
++ */
++ stub?: boolean;
++ }[];
++ /**
++ * Only published documents are ever served.
++ */
++ publication_state?: "draft" | "scheduled" | "published" | "unpublished" | "retracted" | "archived";
++ published_at?: string | null;
++ updated_at?: string | null;
++ /**
++ * The owner's deterministic indexability decision (publishing packages, roadmap §32.3). Search can only make it stricter.
++ */
++ indexability?: {
++ decision: "index" | "noindex";
++ /**
++ * Known reasons: draft, private, members_only, unlisted, not_published, deleted, thin_content, duplicate_without_canonical, unsupported_claims, unsourced, stub_provider, sensitive_unreviewed, ai_unreviewed, missing_canonical_url, third_party_content, owner_decision.
++ *
++ * @maxItems 20
++ */
++ reasons?:
++ | []
++ | [string]
++ | [string, string]
++ | [string, string, string]
++ | [string, string, string, string]
++ | [string, string, string, string, string]
++ | [string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ];
++ };
++}
++
++/** [email protected] (owner: search) */
++/**
++ * search.query@1: the query string of GET /api/v1/search (and, with q/owner/type/limit only, GET /api/v1/suggest), capabilities search.query.run and search.query.delegate. Who sees what is decided by Search per hit: anonymous callers get public, published, indexable documents; a person also restricted documents naming them; a delegating service passes X-OV-Subject. Filters narrow; they never widen visibility.
++ */
++export interface SearchQuery {
++ /**
++ * Words to match (FTS5; empty lists by recency).
++ */
++ q?: string;
++ /**
++ * One owner service (live, community, wiki, …).
++ */
++ owner?: string;
++ /**
++ * One document type within the owner (channel, vod, thread, …).
++ */
++ type?: string;
++ /**
++ * BCP 47 language.
++ */
++ lang?: string;
++ /**
++ * Comma-separated facet keys to count over what the caller can see.
++ */
++ facets?: string;
++ limit?: number | string;
++ /**
++ * next_cursor of the previous page.
++ */
++ cursor?: string;
++ /**
++ * A facet filter (facet.<key>=<value>); repeat the parameter for several values.
++ *
++ * This interface was referenced by `SearchQuery`'s JSON-Schema definition
++ * via the `patternProperty` "^facet\.[a-z][a-z0-9_]{0,39}$".
++ */
++ [k: string]:
++ | string
++ | []
++ | [string]
++ | [string, string]
++ | [string, string, string]
++ | [string, string, string, string]
++ | [string, string, string, string, string]
++ | [string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string, string]
++ | [string, string, string, string, string, string, string, string, string, string, string, string, string, string]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ]
++ | [
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string,
++ string
++ ];
++}
++
++/** [email protected] (owner: search) */
++/**
++ * search.query-result@1: the answer of GET /api/v1/search — one page of hits the caller may see, a cursor for the next page, and facet counts when asked. Never a total count and never an ACL.
++ */
++export interface SearchQueryResult {
++ /**
++ * @maxItems 100
++ */
++ results: {
++ owner: string;
++ type: string;
++ id: string;
++ revision?: number;
++ visibility?: string;
++ title?: string | null;
++ summary?: string | null;
++ canonical_url?: string | null;
++ facets?: {} | null;
++ language?: string | null;
++ authorship?: string | null;
++ provenance?: unknown[] | {} | null;
++ published_at?: string | null;
++ updated_at?: string | null;
++ /**
++ * false for a noindex document the caller may still see.
++ */
++ indexable?: boolean;
++ /**
++ * Escaped text with <mark> around the matches; nothing else.
++ */
++ snippet_html?: string | null;
++ }[];
++ next_cursor: string | null;
++ facets?: {
++ [k: string]:
++ | {
++ value: string;
++ count: number;
++ }[]
++ | undefined;
++ };
++}
++
++/** [email protected] (owner: search) */
++/**
++ * search.write-result@1: the answer of PUT /api/v1/documents/:owner/:type/:id (search.document.write). applied: stored (or a tombstone recorded); unchanged: the same revision with the same content. A revision older than the stored one is a 409 problem search.stale_revision; the same revision with different content is 409 search.revision_conflict.
++ */
++export interface SearchWriteResult {
++ outcome: "applied" | "unchanged";
++ revision: number;
++ /**
++ * What Search held before this write (null: nothing).
++ */
++ stored_revision?: number | null;
++ deleted?: boolean;
++}
++
++/** [email protected] (owner: sources) */
++/**
++ * A registered source (roadmap §4.2 B, Wave 14): where it is fetched from and on what terms. Credentials are referenced by environment-variable NAME only. A source's existence is never permission to fetch: robots and terms are verified before it is enabled, and the fetcher re-checks robots.txt on every run.
++ */
++export interface Source {
++ key: string;
++ name: string;
++ type: "rss" | "atom" | "sitemap" | "jsonld" | "api" | "manual";
++ category: "news" | "blog" | "reviews" | "deals" | "coupons" | "trade";
++ homepage_url?: string | null;
++ /**
++ * @maxItems 20
++ */
++ endpoints:
++ | []
++ | [
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ }
++ ]
++ | [
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ }
++ ]
++ | [
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ }
++ ]
++ | [
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ }
++ ]
++ | [
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ }
++ ]
++ | [
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ }
++ ]
++ | [
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ }
++ ]
++ | [
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ }
++ ]
++ | [
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ }
++ ]
++ | [
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ }
++ ]
++ | [
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
++ [k: string]: string | undefined;
++ };
++ },
++ {
++ url: string;
++ format?: "json" | "xml";
++ items_path?: string;
++ item_kind?: string;
++ fields?: {
++ [k: string]: string | undefined;
++ };
++ extra?: {
[k: string]: string | undefined;
};
},
@@@ -24930,145 +25894,2073 @@ export interface ToolsJobSucceededPayload {
*/
expires_at: string | null;
/**
-- * Where the result files are, never their content, names or the tool's output data (GET /api/v1/jobs/:id has those, for the owner).
++ * Where the result files are, never their content, names or the tool's output data (GET /api/v1/jobs/:id has those, for the owner).
++ */
++ result: {
++ /**
++ * @maxItems 100
++ */
++ files: {
++ /**
++ * n in /api/v1/jobs/:id/files/:n.
++ */
++ index: number;
++ mime: string;
++ size: number;
++ sha256: string;
++ /**
++ * media: stored as an OpenVibe.Media object (TOOLS_JOB_RESULTS=media); local: kept on the satellite until the job expires.
++ */
++ storage: "local" | "media";
++ /**
++ * { media_id, role: output } when storage is media.
++ */
++ media: MediaRef | null;
++ }[];
++ };
++}
++
++/** [email protected] (owner: tools) */
++/**
++ * tools.job.failed v1 (OpenVibe.Tools apps/_shared/jobs/events.js). The job failed: the tool refused the input, it timed out, or it was running when the satellite restarted and its type does not requeue. A cancelled job is not a failure and is not announced as one. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.
++ */
++export interface ToolsJobFailedPayload {
++ job_id: string;
++ /**
++ * The Tools satellite that runs the job (img, audio, docs…).
++ */
++ service: string;
++ /**
++ * Job type, e.g. img.process.
++ */
++ type: string;
++ type_version: number;
++ state: "failed";
++ /**
++ * Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published.
++ */
++ owner: SubjectRef | null;
++ attempts: number;
++ max_attempts: number;
++ created_at: string;
++ /**
++ * The failed job this one retries (POST /api/v1/jobs/:id/retry).
++ */
++ retry_of: string | null;
++ /**
++ * null when the job failed before it ever ran.
++ */
++ started_at: string | null;
++ finished_at: string;
++ /**
++ * When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref).
++ */
++ expires_at: string | null;
++ /**
++ * The job's problem (errors.problem@1) without request or trace ids.
++ */
++ error: {
++ status: number;
++ /**
++ * tools.job.failed, tools.job.timeout, or a code the tool chose (tools.…: tools.pdf.too_many_pages, tools.pdf.wrong_password…).
++ */
++ code: string;
++ /**
++ * Human-readable reason; server paths are scrubbed.
++ */
++ detail: string;
++ };
++ retryable: boolean;
++}
++
++/** [email protected] (owner: media) */
++/**
++ * media.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff action on someone else's media in OpenVibe.Media: a retention hold placed or released (moderation, dmca, admin, evidence), and a staff deletion or visibility change of someone else's VOD, clip or object. Not an owner acting on their own media, and not a creator's own pin. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member (or the service acting for an app's staff). Never carries the media itself.
++ */
++export interface MediaModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
++/** [email protected] (owner: tools) */
++/**
++ * tools.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Tools (apps/gateway and its guard), such as guard blocks and unblocks of an account, an app or an address, and staff actions on someone else's jobs or tool pages. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.
++ */
++export interface ToolsModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
++/** [email protected] (owner: tools) */
++/**
++ * tools.usage.recorded v1 (roadmap WS-N task 4, OpenVibe.Tools apps/_shared/jobs/usage.js). One hour of a developer project's jobs on one Tools satellite, per capability (tools.job.create for POST /api/v1/jobs, tools.tool.run for a tool run) and job type or tool (dimension), in unit jobs: quantity counts the jobs that ended (succeeded or failed; a cancelled job is not counted), errors the failed ones, by code, with the last failures' job ids and trace ids. Counted in the transaction that records a job's end; written to the satellite's outbox after the hour closes. Only jobs of developer-app tokens (they carry a project) are counted; sandbox jobs are counted under env sandbox. The payload is common.usage-recorded@1. Envelope: subject { type: project, id: <project_id> }, visibility internal, priority low, actor service:tools.
++ */
++export interface ToolsUsageRecordedPayload {
++ /**
++ * The developer project whose app token did the work.
++ */
++ project_id: string;
++ /**
++ * The environment of that app token.
++ */
++ env: "sandbox" | "production";
++ /**
++ * The capability the usage counts against: tools.job.create, tools.tool.run, events.app.publish, events.app.subscribe.
++ */
++ capability: string;
++ /**
++ * Optional finer key inside the capability, as the producer names it: a job type (img.process) or a tool id (image-resize). Never the id of a person, an app, a request or a job.
++ */
++ dimension?: string;
++ /**
++ * What quantity counts (jobs, events, deliveries, requests, bytes, tokens): the same word a Network project quota uses for its unit.
++ */
++ unit: string;
++ /**
++ * The length of the window.
++ */
++ window: "hour" | "day";
++ /**
++ * UTC, on the hour (midnight for a day).
++ */
++ window_start: string;
++ /**
++ * window_start plus the window, exclusive.
++ */
++ window_end: string;
++ /**
++ * Units used in the window, by work that succeeded and work that failed alike.
++ */
++ quantity: number;
++ /**
++ * Operations in the window that failed or were refused: a job that failed, a publish refused with a problem, a webhook attempt without a 2xx. A refused operation may use no unit, so errors can exceed quantity.
++ */
++ errors: number;
++ /**
++ * The errors by problem code (at most 20 codes; the rest are only in errors).
++ */
++ error_codes?: {
++ [k: string]: number | undefined;
++ };
++ /**
++ * The last failures of the window, newest first, so a developer can find them in their own logs.
++ *
++ * @maxItems 10
++ */
++ samples?:
++ | []
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ];
++ /**
++ * 1 when absent. A producer that re-emits a window with corrected totals raises it.
*/
-- result: {
-- /**
-- * @maxItems 100
-- */
-- files: {
-- /**
-- * n in /api/v1/jobs/:id/files/:n.
-- */
-- index: number;
-- mime: string;
-- size: number;
-- sha256: string;
-- /**
-- * media: stored as an OpenVibe.Media object (TOOLS_JOB_RESULTS=media); local: kept on the satellite until the job expires.
-- */
-- storage: "local" | "media";
-- /**
-- * { media_id, role: output } when storage is media.
-- */
-- media: MediaRef | null;
-- }[];
-- };
++ revision?: number;
}
--/** [email protected] (owner: tools) */
++/** [email protected] (owner: events) */
/**
-- * tools.job.failed v1 (OpenVibe.Tools apps/_shared/jobs/events.js). The job failed: the tool refused the input, it timed out, or it was running when the satellite restarted and its type does not requeue. A cancelled job is not a failure and is not announced as one. Emitted in the transaction that records the transition, beside the job's own SSE event. Carries ids, type, owner, state, times and result locations; never the job's input, its files, its output data or a session. Jobs of sandbox app tokens are not announced. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:tools.
++ * events.usage.recorded v1 (roadmap WS-N task 4, OpenVibe.Events server/usage.js). One hour of a developer project's use of OpenVibe.Events in one environment: events.app.publish in unit events (quantity counts the events stored; errors the publish requests refused with a problem, such as 429 events.quota_exceeded or 403 events.type_not_allowed) and events.app.subscribe in unit deliveries (quantity counts webhook delivery attempts to the project's subscriptions; errors the attempts without a 2xx, as events.delivery.http_<status>, events.delivery.timeout or events.delivery.failed). Counted in the transaction that stores the events or records the attempt (a refusal right after it is answered); Events stores the rollup as its own event after the hour closes, in the same transaction that marks it sent. The payload is common.usage-recorded@1. Envelope: source events, subject { type: project, id: <project_id> }, visibility internal, priority low, actor service:events.
*/
--export interface ToolsJobFailedPayload {
-- job_id: string;
-- /**
-- * The Tools satellite that runs the job (img, audio, docs…).
-- */
-- service: string;
-- /**
-- * Job type, e.g. img.process.
-- */
-- type: string;
-- type_version: number;
-- state: "failed";
-- /**
-- * Who submitted the job: a user (user:usr_…), a service, app or mod principal. null for an anonymous browser session; the session is never published.
-- */
-- owner: SubjectRef | null;
-- attempts: number;
-- max_attempts: number;
-- created_at: string;
-- /**
-- * The failed job this one retries (POST /api/v1/jobs/:id/retry).
-- */
-- retry_of: string | null;
++export interface EventsUsageRecordedPayload {
/**
-- * null when the job failed before it ever ran.
++ * The developer project whose app token did the work.
*/
-- started_at: string | null;
-- finished_at: string;
++ project_id: string;
/**
-- * When the job and its result files are pruned; null while something references the result (PUT /api/v1/jobs/:id/references/:ref).
++ * The environment of that app token.
*/
-- expires_at: string | null;
++ env: "sandbox" | "production";
/**
-- * The job's problem (errors.problem@1) without request or trace ids.
++ * The capability the usage counts against: tools.job.create, tools.tool.run, events.app.publish, events.app.subscribe.
*/
-- error: {
-- status: number;
-- /**
-- * tools.job.failed, tools.job.timeout, or a code the tool chose (tools.…: tools.pdf.too_many_pages, tools.pdf.wrong_password…).
-- */
-- code: string;
-- /**
-- * Human-readable reason; server paths are scrubbed.
-- */
-- detail: string;
-- };
-- retryable: boolean;
--}
--
--/** [email protected] (owner: media) */
--/**
-- * media.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff action on someone else's media in OpenVibe.Media: a retention hold placed or released (moderation, dmca, admin, evidence), and a staff deletion or visibility change of someone else's VOD, clip or object. Not an owner acting on their own media, and not a creator's own pin. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member (or the service acting for an app's staff). Never carries the media itself.
-- */
--export interface MediaModerationActionPayload {
++ capability: string;
/**
-- * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ * Optional finer key inside the capability, as the producer names it: a job type (img.process) or a tool id (image-resize). Never the id of a person, an app, a request or a job.
*/
-- action: string;
-- target: {
-- /**
-- * What was acted on, in the service's own words (post, page, release, user, player, …).
-- */
-- type: string;
-- id: string;
-- /**
-- * Whose content or account it was, when known.
-- */
-- owner_subject?: string | null;
-- };
++ dimension?: string;
/**
-- * The staff member or moderator (null only for a service acting without a person).
++ * What quantity counts (jobs, events, deliveries, requests, bytes, tokens): the same word a Network project quota uses for its unit.
*/
-- actor_subject: string | null;
-- reason?: string | null;
++ unit: string;
/**
-- * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ * The length of the window.
*/
-- details?: {};
--}
--
--/** [email protected] (owner: tools) */
--/**
-- * tools.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Tools (apps/gateway and its guard), such as guard blocks and unblocks of an account, an app or an address, and staff actions on someone else's jobs or tool pages. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.
-- */
--export interface ToolsModerationActionPayload {
++ window: "hour" | "day";
/**
-- * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ * UTC, on the hour (midnight for a day).
*/
-- action: string;
-- target: {
-- /**
-- * What was acted on, in the service's own words (post, page, release, user, player, …).
-- */
-- type: string;
-- id: string;
-- /**
-- * Whose content or account it was, when known.
-- */
-- owner_subject?: string | null;
++ window_start: string;
++ /**
++ * window_start plus the window, exclusive.
++ */
++ window_end: string;
++ /**
++ * Units used in the window, by work that succeeded and work that failed alike.
++ */
++ quantity: number;
++ /**
++ * Operations in the window that failed or were refused: a job that failed, a publish refused with a problem, a webhook attempt without a 2xx. A refused operation may use no unit, so errors can exceed quantity.
++ */
++ errors: number;
++ /**
++ * The errors by problem code (at most 20 codes; the rest are only in errors).
++ */
++ error_codes?: {
++ [k: string]: number | undefined;
};
/**
-- * The staff member or moderator (null only for a service acting without a person).
++ * The last failures of the window, newest first, so a developer can find them in their own logs.
++ *
++ * @maxItems 10
*/
-- actor_subject: string | null;
-- reason?: string | null;
++ samples?:
++ | []
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ]
++ | [
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ },
++ {
++ at: string;
++ code: string;
++ /**
++ * The HTTP status the failure was answered with, when there was one.
++ */
++ status?: number;
++ /**
++ * The W3C trace id of the request (or event) that failed.
++ */
++ trace_id?: string;
++ /**
++ * What failed, when the project can look it up with its own token: a job, an event, an AI run, a Media object. Never a subject.
++ */
++ ref?: string;
++ }
++ ];
/**
-- * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ * 1 when absent. A producer that re-emits a window with corrected totals raises it.
*/
-- details?: {};
++ revision?: number;
}
/** [email protected] (owner: live) */
@@@ -26310,6 +29202,106 @@ export interface NetworkRealtimeTicketResult {
subject: string;
}
++/** [email protected] (owner: network) */
++/**
++ * network.project-usage-result@1 (roadmap WS-N task 4, ADR-014): what GET /api/v1/projects/:project/usage?days=&env= on OpenVibe.Network answers a project's owner or admin (or staff): the project's usage per day, service, capability and unit, added up from the <service>.usage.recorded rollups (common.usage-recorded@1) the owning services emit after each hour closes; the project's recorded quotas with what the current window has used; and its recent failures with their codes and trace ids. Counts only, never who did what: no subject id, address, input or content.
++ */
++export interface ProjectUsageResult {
++ project_id: string;
++ /**
++ * The environments counted (the env query parameter; all by default).
++ */
++ env: "all" | "sandbox" | "production";
++ range: {
++ days: number;
++ /**
++ * First UTC day counted.
++ */
++ from: string;
++ /**
++ * Last UTC day counted (today).
++ */
++ to: string;
++ };
++ generated_at: string;
++ /**
++ * When the newest rollup for this project arrived; null before the first.
++ */
++ last_recorded_at: string | null;
++ /**
++ * How current the numbers are, in words (rollups arrive after each hour closes).
++ */
++ freshness: string;
++ /**
++ * The whole range per service, capability, unit and environment.
++ */
++ totals: {
++ service: Service;
++ capability: Capability;
++ unit: Unit;
++ env: Env;
++ quantity: Count;
++ errors: Count;
++ }[];
++ /**
++ * One row per UTC day, service, capability, dimension, unit and environment that saw usage; days without any are absent. Newest day first.
++ */
++ daily: {
++ day: string;
++ service: Service;
++ capability: Capability;
++ dimension: string | null;
++ unit: Unit;
++ env: Env;
++ quantity: Count;
++ errors: Count;
++ }[];
++ /**
++ * The project's recorded quotas (GET /api/v1/projects/:project/quotas), each with what its current window has used, in every environment together. used is null where the rollups cannot tell (a minute or hour window, or a unit no service reports for that capability).
++ */
++ quotas: {
++ capability: Capability;
++ limit: number;
++ window: "minute" | "hour" | "day" | "month" | "total";
++ unit: Unit;
++ /**
++ * The audience of the service that enforces it (openvibe.<owner>).
++ */
++ enforced_by: string | null;
++ used: number | null;
++ remaining: number | null;
++ /**
++ * Start of the current window counted (today, this month); null for total and when used is null.
++ */
++ window_start: string | null;
++ note: string | null;
++ }[];
++ errors: {
++ total: Count;
++ by_code: {
++ service: Service;
++ capability: Capability;
++ code: Code;
++ count: Count;
++ }[];
++ /**
++ * The newest failures the rollups sampled, newest first.
++ *
++ * @maxItems 50
++ */
++ recent: {
++ at: string;
++ env: Env;
++ service: Service;
++ capability: Capability;
++ code: Code;
++ status: number | null;
++ trace_id: string | null;
++ ref: string | null;
++ }[];
++ };
++}
++
/** [email protected] (owner: network) */
/**
* network.staff-list-result@1: the answer of network.staff.read on OpenVibe.Network. GET /api/v1/staff/moderators[?service=chat|live|community|pastes|calls] (a service token holding network.staff.read, or a signed-in person holding staff.moderation.logs) → { service, staff }: the network's global moderators, admins and the owner who are not banned, with the staff capabilities their role holds in the contracts staff map; ?service keeps those holding the capability that moderates that service (another service name keeps those holding staff.content.moderate; an invalid value is ignored and answered as null). Answered with Cache-Control: private, no-store; a person without staff.moderation.logs gets 403 { error: forbidden }.
ddiff --git a/manifests/services/events.json b/manifests/services/events.json
iindex 71684dde..dfefb056 100644
---- a/manifests/services/events.json
++++ b/manifests/services/events.json
@@@ -27,7 +27,9 @@
"events.app.read",
"events.app.subscribe"
],
-- "eventsProduced": [],
++ "eventsProduced": [
++ "events.usage.recorded"
++ ],
"eventsConsumed": [],
"namespacesOwned": [],
"lifecycle": {
ddiff --git a/manifests/services/network.json b/manifests/services/network.json
iindex 29f01b4b..1be2704e 100644
---- a/manifests/services/network.json
++++ b/manifests/services/network.json
@@@ -69,6 +69,7 @@
"coupons.moderation.action",
"deals.moderation.action",
"deals.watch.matched",
++ "events.usage.recorded",
"games.moderation.action",
"live.moderation.action",
"live.stream.started",
@@@ -77,6 +78,7 @@
"reviews.moderation.action",
"tips.interaction.moderated",
"tools.moderation.action",
++ "tools.usage.recorded",
"trade.alert.triggered",
"trade.moderation.action",
"wiki.moderation.action"
ddiff --git a/manifests/services/tools.json b/manifests/services/tools.json
iindex 153cddf6..015fe997 100644
---- a/manifests/services/tools.json
++++ b/manifests/services/tools.json
@@@ -39,7 +39,8 @@
"tools.job.started",
"tools.job.succeeded",
"tools.job.failed",
-- "tools.moderation.action"
++ "tools.moderation.action",
++ "tools.usage.recorded"
],
"eventsConsumed": [
"network.user.token_valid_after"
ddiff --git a/package.json b/package.json
iindex 082bd377..be51f3ed 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.62.0",
++ "version": "0.63.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",
ddiff --git a/test/run.js b/test/run.js
iindex ea452328..8a24bf57 100644
---- a/test/run.js
++++ b/test/run.js
@@@ -791,6 +791,35 @@ await assert.rejects(failing.getToken(), /401: invalid_client/);
ok(R({ ...r, expires_in: 300 }) && !R({ ...r, expires_in: 301 }), 'a ticket lives at most 300 s');
}
++// ── Project usage (WS-N task 4, ADR-014): one shared rollup payload, one event per producer ──
++// Every <service>.usage.recorded is common.usage-recorded@1, owned by its service and consumed by
++// Network (the project dashboard). A rollup counts, it never says who: no subject, app, address,
++// input or message, and at most ten sampled failures.
++{
++ const usage = contracts.catalog.filter(c => /\.usage\.recorded$/.test(c.id));
++ ok(usage.length >= 2 && ['tools', 'events'].every(svc => usage.some(c => c.id === `${svc}.usage.recorded`)), 'tools and events publish usage rollups');
++ for (const c of usage) {
++ ok(c.owner === c.id.split('.')[0] && c.adr === 'ADR-014', `${c.id} is owned by ${c.id.split('.')[0]}`);
++ ok(contracts.schema(c.id).$ref === '../../common/usage-recorded.v1.json', `${c.id} is a common.usage-recorded@1`);
++ ok(services.get('network').eventsConsumed.includes(c.id), `network consumes ${c.id} into project usage`);
++ }
++ const U = (v) => contracts.validate('common.usage-recorded@1', v).valid;
++ const base = JSON.parse(fs.readFileSync(path.join(ROOT, 'fixtures/common.usage-recorded/valid/tools-jobs-hour.json'), 'utf8'));
++ ok(U(base), 'an hour of jobs');
++ for (const k of ['project_id', 'env', 'capability', 'unit', 'window', 'window_start', 'window_end', 'quantity', 'errors']) ok(!U({ ...base, [k]: undefined }), `a rollup needs ${k}`);
++ for (const k of ['subject', 'owner', 'actor', 'user_id', 'app_id', 'ip', 'session', 'input', 'request_id']) ok(!U({ ...base, [k]: 'x' }), `a rollup never carries ${k}`);
++ const props = Object.keys(contracts.schema('common.usage-recorded').properties);
++ ok(!props.some(p => /subject|owner|actor|user|ip|session|address/.test(p)), 'no field of the rollup names who did it');
++ const sample = base.samples[0];
++ ok(!U({ ...base, samples: Array.from({ length: 11 }, () => sample) }), 'at most ten sampled failures: a rollup, not a request log');
++ for (const ref of ['usr_01JAB2C3D4E5F6G7H8J9K0MNPQ', 'app_01JAB2C3D4E5F6G7H8J9K0MNPQ', 'req_01JAB2C3D4E5F6G7H8J9K0MNPQ']) ok(!U({ ...base, samples: [{ ...sample, ref }] }), `a sample never refers to ${ref.slice(0, 3)}_`);
++ ok(!U({ ...base, window: 'minute' }) && U({ ...base, window: 'day' }), 'windows are an hour or a day');
++ const R = (v) => contracts.validate('network.project-usage-result@1', v).valid;
++ const res = JSON.parse(fs.readFileSync(path.join(ROOT, 'fixtures/network.project-usage-result/valid/dashboard.json'), 'utf8'));
++ ok(R(res) && !R({ ...res, range: { ...res.range, days: 91 } }), 'a dashboard covers at most 90 days');
++ ok(res.errors.recent.every(e => contracts.validate('common.usage-recorded@1', { ...base, samples: [{ at: e.at, code: e.code, ...(e.status ? { status: e.status } : {}), ...(e.trace_id ? { trace_id: e.trace_id } : {}), ...(e.ref ? { ref: e.ref } : {}) }] }).valid), 'the dashboard\'s recent failures are the rollups\' samples');
++}
++
// ── Capability schemas (WS-C task 4): a ratchet over compatibility/capability-schema-gaps.json ──
execFileSync(process.execPath, [path.join(__dirname, 'capability-schemas.test.js')], { stdio: 'inherit' });