openvibers/openvibe.contracts Commits

AI Exchange P1 contracts (0.284.0) (#293)

OP OpenVibers committed 9 days ago · cd7a5e3839 · 157 files, +5451 −106

* AI Exchange P1 contracts (0.284.0)

- Extend ai.model@1 to a catalog entry with id, family, variant, quant, revision, sha256, license, context, capabilities, modalities, retail_rate and availability; keep existing docs valid.
- Add ai.model-artifact@1, ai.inference-deployment@1, ai.route-explain@1, ai.model-list@1 and ai.model-read-result@1.
- Add billing.inference-hold@1, billing.inference-settlement@1 and the internal route request/result contracts for POST /internal/inference/holds, /holds/:id/extend, /holds/:id/settle and /holds/:id/release.
- Extend platform.rate-card@1 with optional side, version and effective_to.
- Register public capabilities ai.inference.create and ai.model.read, and internal billing.inference.hold.* capabilities; list them in the ai and billing service manifests.
- Bump version to 0.284.0, update CHANGELOG and README counts, regenerate types/OpenAPI, add fixtures.

Checked: OV_ALLOW_SUITE=1 node test/run.js (24627 checks passed), npm run generate, gitleaks --pre-commit --staged.

* D9: billing.credit-* (Billing's general prepaid USD credit), not billing.inference-*

The owner decided (11-03) on two currencies: Vibes stay the social one; Credits are the
topped-up USD balance that metered developer services spend (AI first, then compute).
Renamed before release: billing.credit-hold*, billing.credit-settlement@1, capabilities
billing.credit.hold.{create,extend,settle,release}, routes /internal/credits/*, quota class
billing-credit. Added service (which metered service spends it) on the hold and its create
request, and shortfall_nano_usd on the settlement and the settle result. Suite: 24627
checks pass; compat vs v0.283.0: no breaking changes; generated/ up to date.

* D9: billing.credit.sandbox.grant with its request and result schemas; BillingCredit* type names

Synthetic credit for tests and staging (Billing answers only with BILLING_CREDITS_SANDBOX=1,
a test transaction): billing.credit-sandbox-grant-request@1 and -result@1 with fixtures, the
capability on the Billing service manifest. Generated type titles read BillingCredit*.
Suite: 24672 checks pass; compat vs v0.283.0: no breaking changes.

Changes

ddiff --git a/.gitleaks.toml b/.gitleaks.toml
iindex 872cbcd0..517be4b9 100644
---- a/.gitleaks.toml
++++ b/.gitleaks.toml
@@@ -57,3 +57,9 @@ description = "Sample realtime tickets in network.realtime-ticket-result fixture
condition = "AND"
paths = ['''^fixtures/network\.realtime\-ticket\-result/(valid|invalid)/[a-z-]+\.json$''']
regexes = ['''^eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\.eyJ0eXAiOiJyZWFsdGltZSJ9\.c2lnbmF0dXJl$''']
++
++[[allowlists]]
++description = "Sample model_key in ai.model-list@1 fixture: a model name placeholder, not an API key"
++condition = "AND"
++paths = ['''^fixtures/ai\.model-list/valid/two-models\.json$''']
++regexes = ['''^llama_3\.1_8b_instruct$''']
ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 01afb568..8a160783 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -1,5 +1,15 @@
# Changelog
++## 0.284.0 — 2026-11-03
++
++- **AI Exchange contract foundation (D1-D4, D8)**:
++ - `ai.model@1` extended to a catalog entry with optional `id`, `family`, `variant`, `quant`, `revision`, `sha256`, `license`, `context`, `capabilities`, `modalities`, `retail_rate` (nano-USD per million tokens) and `availability`; existing provider-model documents stay valid.
++ - New schemas: `ai.model-artifact@1`, `ai.inference-deployment@1`, `ai.route-explain@1`, `ai.model-list@1`, `ai.model-read-result@1`.
++ - New Billing inference subledger schemas: `billing.credit-hold@1`, `billing.credit-settlement@1`, plus internal route contracts for `POST /internal/credits/holds`, `/holds/:id/extend`, `/holds/:id/settle` and `/holds/:id/release`.
++ - `platform.rate-card@1` compatibly extended with optional `side` (`supply`|`retail`), `version` and `effective_to`; existing documents stay valid.
++ - New public capability `ai.inference.create` for developer-app inference on `POST /v1/chat/completions` and `POST /v1/messages`, and public capability `ai.model.read` for `GET /v1/models` and `GET /v1/models/:id`.
++ - All new schemas ship valid and invalid fixtures, including wrong money type and float money value examples.
++
## 0.283.0 — 2026-11-03
- **`chat.timeline-result@1` buckets may say how many people wrote** (`people`, optional): a signed-in user, an
ddiff --git a/README.md b/README.md
iindex 0da2b65b..ada4dc1a 100644
---- a/README.md
++++ b/README.md
@@@ -2,7 +2,7 @@
> Machine-readable contracts for the whole OpenVibe network.
--**Status:** alpha (first released in Wave 1; the release is package.json's `version`, tagged). At v0.210.0: 798 schemas in `contracts/catalog.json`, 45 service manifests, 348 capability manifests, 13 namespace manifests and the staff capability map. Service repositories run `openvibe-contracts-check` and the pin-drift check in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Consumers pin release tags (each repository's `STATUS.json` names its pin). `node scripts/docs-currency.js <root>` checks every checkout's README and `STATUS.json` against its code: the pins and versions they name, a library's consumer list, the deployed flag against the manifest, README links, and the paths and migrations they name. `node scripts/estate.js` generates `docs/ESTATE.md` and `manifests/repositories/` from the estate checkouts, and `--check` fails on drift (plan T1 step 3).
++**Status:** alpha (first released in Wave 1; the release is package.json's `version`, tagged). At v0.284.0: 969 schemas in `contracts/catalog.json`, 45 service manifests, 440 capability manifests, 13 namespace manifests and the staff capability map. Service repositories run `openvibe-contracts-check` and the pin-drift check in CI, and deployed services verify service and app tokens with `serviceAuth` in production. Consumers pin release tags (each repository's `STATUS.json` names its pin). `node scripts/docs-currency.js <root>` checks every checkout's README and `STATUS.json` against its code: the pins and versions they name, a library's consumer list, the deployed flag against the manifest, README links, and the paths and migrations they name. `node scripts/estate.js` generates `docs/ESTATE.md` and `manifests/repositories/` from the estate checkouts, and `--check` fails on drift (plan T1 step 3).
**Plan:** OpenVibe Development Roadmap, Wave 1 (implementation plan rev 3, §3.1 and §18.11).
**License:** AGPL-3.0 (same as every OpenVibe service).
ddiff --git a/contracts/ai/inference-deployment.v1.json b/contracts/ai/inference-deployment.v1.json
nnew file mode 100644
iindex 00000000..37b9ad15
---- /dev/null
++++ b/contracts/ai/inference-deployment.v1.json
@@@ -0,0 +1,158 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/inference-deployment.v1.json",
++ "title": "AiInferenceDeployment",
++ "description": "ai.inference-deployment@1: a loaded inference deployment for a pinned artifact. Describes where it runs, its trust tier, commercial rights, health, limits and the rate card that prices its supply cost.",
++ "type": "object",
++ "required": [
++ "id",
++ "artifact_id",
++ "region",
++ "status"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "maxLength": 256,
++ "description": "Deployment id, unique within OpenVibe.AI."
++ },
++ "artifact_id": {
++ "type": "string",
++ "pattern": "^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+(:[a-zA-Z0-9._-]+)?@[a-zA-Z0-9._-]+$",
++ "maxLength": 256,
++ "description": "Exact artifact id this deployment loads."
++ },
++ "region": {
++ "type": "string",
++ "description": "Deployment region or residency label."
++ },
++ "trust_tier": {
++ "type": "string",
++ "enum": [
++ "first_party",
++ "owner_node",
++ "approved_wholesale",
++ "unverified"
++ ],
++ "description": "Who operates the deployment and what data may reach it."
++ },
++ "operator": {
++ "type": "string",
++ "description": "Operator slug or `owner:<subject_id>` for a private node."
++ },
++ "host": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Internal host identifier; never exposed to untrusted callers."
++ },
++ "endpoint": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "uri",
++ "description": "Service endpoint for this deployment (internal or partner)."
++ },
++ "status": {
++ "type": "string",
++ "enum": [
++ "warming",
++ "ready",
++ "full",
++ "degraded",
++ "offline"
++ ],
++ "description": "Current load/health state."
++ },
++ "loaded_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "limits": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "max_concurrency": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "context_tokens": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "max_output_tokens": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ },
++ "supply_rate_card_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Reference to the supply rate card (platform.rate-card@1) that prices this deployment."
++ },
++ "provider_access": {
++ "type": "string",
++ "enum": [
++ "OWN_DEPLOYMENT",
++ "AUTHORIZED_WHOLESALE",
++ "CUSTOMER_BYOK",
++ "RESTRICTED",
++ "UNVERIFIED"
++ ],
++ "description": "Commercial eligibility for paid managed routes."
++ },
++ "commercial_rights": {
++ "type": "string",
++ "enum": [
++ "OWN_DEPLOYMENT",
++ "AUTHORIZED_WHOLESALE",
++ "CUSTOMER_BYOK",
++ "RESTRICTED",
++ "UNVERIFIED"
++ ],
++ "description": "Resale authority recorded for this deployment."
++ },
++ "privacy_policy": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Retention/zero-retention policy label."
++ },
++ "health": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "healthy": {
++ "type": "boolean"
++ },
++ "last_check_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "reason": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++}
ddiff --git a/contracts/ai/model-artifact.v1.json b/contracts/ai/model-artifact.v1.json
nnew file mode 100644
iindex 00000000..b7fd35c5
---- /dev/null
++++ b/contracts/ai/model-artifact.v1.json
@@@ -0,0 +1,78 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/model-artifact.v1.json",
++ "title": "AiModelArtifact",
++ "description": "ai.model-artifact@1: one pinned revision/variant/quantization of a model family. Identified by an exact id (`<org>/<model>[:<quant>]@<revision>`), with its weight SHA-256, tokenizer revision, license and provenance.",
++ "type": "object",
++ "required": [
++ "id",
++ "family",
++ "variant",
++ "quant",
++ "revision",
++ "sha256",
++ "license"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+(:[a-zA-Z0-9._-]+)?@[a-zA-Z0-9._-]+$",
++ "maxLength": 256,
++ "description": "Exact artifact id: `<org>/<model>[:<quant>]@<revision>`."
++ },
++ "family": {
++ "type": "string",
++ "description": "Underlying model family, e.g. `qwen3`."
++ },
++ "variant": {
++ "type": "string",
++ "enum": [
++ "original",
++ "abliterated",
++ "finetuned",
++ "merged",
++ "lora"
++ ],
++ "description": "How the weights were produced."
++ },
++ "quant": {
++ "type": "string",
++ "description": "Quantization label, e.g. `q4_k_m` or `fp16`."
++ },
++ "revision": {
++ "type": "string",
++ "description": "Artifact revision or commit hash."
++ },
++ "sha256": {
++ "type": "string",
++ "pattern": "^[a-f0-9]{64}$",
++ "description": "SHA-256 of the weight file or signed manifest."
++ },
++ "tokenizer_revision": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Tokenizer/chat-template revision used with this artifact."
++ },
++ "license": {
++ "type": "string",
++ "description": "SPDX license identifier or custom license name."
++ },
++ "source_url": {
++ "type": "string",
++ "format": "uri",
++ "description": "Canonical download or provenance URL."
++ },
++ "context": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "Maximum context length in tokens."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++}
ddiff --git a/contracts/ai/model-list.v1.json b/contracts/ai/model-list.v1.json
nnew file mode 100644
iindex 00000000..7e1b9a43
---- /dev/null
++++ b/contracts/ai/model-list.v1.json
@@@ -0,0 +1,32 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/model-list.v1.json",
++ "title": "AiModelList",
++ "description": "ai.model-list@1: paginated list of model catalog entries returned by GET /v1/models.",
++ "type": "object",
++ "required": [
++ "models",
++ "count"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "models": {
++ "type": "array",
++ "items": {
++ "$ref": "model.v1.json"
++ }
++ },
++ "count": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "Number of models in this response."
++ },
++ "cursor": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Opaque cursor for the next page."
++ }
++ }
++}
ddiff --git a/contracts/ai/model-read-result.v1.json b/contracts/ai/model-read-result.v1.json
nnew file mode 100644
iindex 00000000..6dc320cf
---- /dev/null
++++ b/contracts/ai/model-read-result.v1.json
@@@ -0,0 +1,15 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/model-read-result.v1.json",
++ "title": "AiModelReadResult",
++ "description": "ai.model-read-result@1: response from GET /v1/models (a list) or GET /v1/models/:id (a single model).",
++ "type": "object",
++ "anyOf": [
++ {
++ "$ref": "model-list.v1.json"
++ },
++ {
++ "$ref": "model.v1.json"
++ }
++ ]
++}
ddiff --git a/contracts/ai/model.v1.json b/contracts/ai/model.v1.json
iindex 61d8368f..e0b9bdd0 100644
---- a/contracts/ai/model.v1.json
++++ b/contracts/ai/model.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/ai/model.v1.json",
"title": "AiModel",
-- "description": "ai.model@1: one model of a provider in OpenVibe.AI's registry (server/registry.js decodeModel): type, status, limits, per-million-token costs and what it supports.",
++ "description": "ai.model@1: a catalog entry for one model in OpenVibe.AI's registry. The legacy provider/model fields stay for existing readers; new catalog fields (id, family, variant, quant, revision, sha256, license, context, capabilities, modalities, retail rate and availability) are optional so older documents remain valid. Exact ids follow `<org>/<model>[:<quant>]@<revision>`; dynamic aliases such as `openvibe/auto` may be enumerated in `aliases`. Money fields are integer nano-USD per million tokens, as strings.",
"type": "object",
"required": [
"provider_key",
@@@ -12,6 +12,13 @@
"cost",
"supports"
],
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
"properties": {
"provider_key": {
"type": "string"
@@@ -92,6 +99,128 @@
}
}
},
++ "id": {
++ "type": "string",
++ "pattern": "^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+(:[a-zA-Z0-9._-]+)?@[a-zA-Z0-9._-]+$",
++ "description": "Exact model id: `<org>/<model>[:<quant>]@<revision>`.",
++ "maxLength": 256
++ },
++ "aliases": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "enum": [
++ "openvibe",
++ "openvibe/auto",
++ "openvibe/economy",
++ "openvibe/balanced",
++ "openvibe/fast",
++ "openvibe/private"
++ ]
++ },
++ "description": "Dynamic policy aliases that resolve to this model."
++ },
++ "family": {
++ "type": "string",
++ "description": "Creator and underlying model family, e.g. `qwen3`."
++ },
++ "variant": {
++ "type": "string",
++ "enum": [
++ "original",
++ "abliterated",
++ "finetuned",
++ "merged",
++ "lora"
++ ],
++ "description": "How the weights were produced."
++ },
++ "quant": {
++ "type": "string",
++ "description": "Quantization label, e.g. `q4_k_m`."
++ },
++ "revision": {
++ "type": "string",
++ "description": "Artifact revision or commit hash."
++ },
++ "sha256": {
++ "type": "string",
++ "pattern": "^[a-f0-9]{64}$",
++ "description": "SHA-256 of the weight file or manifest."
++ },
++ "license": {
++ "type": "string",
++ "description": "SPDX license identifier or custom license name."
++ },
++ "context": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "Maximum context length in tokens."
++ },
++ "capabilities": {
++ "type": "object",
++ "properties": {
++ "tools": {
++ "type": "boolean"
++ },
++ "json": {
++ "type": "boolean"
++ },
++ "vision": {
++ "type": "boolean"
++ },
++ "stream": {
++ "type": "boolean"
++ }
++ }
++ },
++ "modalities": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "enum": [
++ "text",
++ "image",
++ "audio",
++ "video"
++ ]
++ },
++ "description": "Input/output modalities the model supports."
++ },
++ "retail_rate": {
++ "type": "object",
++ "description": "Customer retail price, immutable rate version and nano-USD per-million-token component rates.",
++ "properties": {
++ "version": {
++ "type": "string"
++ },
++ "input_nano_usd_per_m": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "cached_read_nano_usd_per_m": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "cache_write_nano_usd_per_m": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "output_nano_usd_per_m": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "per_request_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++ },
++ "availability": {
++ "type": "string",
++ "enum": [
++ "available",
++ "limited",
++ "unavailable",
++ "deprecated"
++ ],
++ "description": "Whether the model can be chosen for new requests."
++ },
"metadata": {
"type": "object"
},
ddiff --git a/contracts/ai/route-explain.v1.json b/contracts/ai/route-explain.v1.json
nnew file mode 100644
iindex 00000000..ac3af413
---- /dev/null
++++ b/contracts/ai/route-explain.v1.json
@@@ -0,0 +1,124 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/route-explain.v1.json",
++ "title": "AiRouteExplain",
++ "description": "ai.route-explain@1: why OpenVibe.AI chose a model/deployment for a request. Lists the chosen candidate, rejected candidates with reasons, the objective (policy), the pre-flight cost estimate and the final actual cost. Money fields are integer nano-USD, as strings.",
++ "type": "object",
++ "required": [
++ "chosen",
++ "rejected",
++ "objective",
++ "estimate"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "chosen": {
++ "type": "string",
++ "description": "Exact id of the model/deployment that answered."
++ },
++ "rejected": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "reason"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "description": "Exact id of the rejected candidate."
++ },
++ "reason": {
++ "type": "string",
++ "description": "Human-readable exclusion reason, without secrets or internal host IPs."
++ }
++ }
++ }
++ },
++ "objective": {
++ "type": "string",
++ "enum": [
++ "cheapest",
++ "balanced",
++ "fastest",
++ "best",
++ "private",
++ "exact"
++ ],
++ "description": "Routing policy used for this request."
++ },
++ "estimate": {
++ "type": "object",
++ "required": [
++ "charge_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "charge_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "input_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cached_read_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output_tokens": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "actual": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "charge_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "input_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cached_read_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output_tokens": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "policy": {
++ "type": "string",
++ "description": "Name of the policy/profile that produced the choice."
++ },
++ "fallback_reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "If the first choice could not be used, why the chosen deployment was selected instead."
++ }
++ }
++}
ddiff --git a/contracts/billing/credit-hold-create-request.v1.json b/contracts/billing/credit-hold-create-request.v1.json
nnew file mode 100644
iindex 00000000..122fa6c0
---- /dev/null
++++ b/contracts/billing/credit-hold-create-request.v1.json
@@@ -0,0 +1,58 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold-create-request.v1.json",
++ "title": "BillingCreditHoldCreateRequest",
++ "description": "billing.credit-hold-create-request@1: body of POST /internal/credits/holds. Reserves inference credit up to max_nano_usd against a (subject, project_id) account, priced at the given immutable rate version.",
++ "type": "object",
++ "required": [
++ "idempotency_key",
++ "subject",
++ "project_id",
++ "max_nano_usd",
++ "rate_version",
++ "request_fingerprint"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "idempotency_key": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9._:-]{8,128}$"
++ },
++ "subject": {
++ "$ref": "../identity/subject-ref.v1.json"
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "credential_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^cred_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "max_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "rate_version": {
++ "type": "string"
++ },
++ "request_fingerprint": {
++ "type": "string",
++ "maxLength": 1024
++ },
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{0,39}$",
++ "description": "Which metered service spends the credit (D9): ai, run, ... Defaults to ai."
++ }
++ }
++}
ddiff --git a/contracts/billing/credit-hold-create-result.v1.json b/contracts/billing/credit-hold-create-result.v1.json
nnew file mode 100644
iindex 00000000..4ebd074e
---- /dev/null
++++ b/contracts/billing/credit-hold-create-result.v1.json
@@@ -0,0 +1,37 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold-create-result.v1.json",
++ "title": "BillingCreditHoldCreateResult",
++ "description": "billing.credit-hold-create-result@1: response from POST /internal/credits/holds. Returns the created or replayed hold.",
++ "type": "object",
++ "required": [
++ "hold_id",
++ "held_nano_usd",
++ "available_nano_usd",
++ "lease_expires_at"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "hold_id": {
++ "type": "string",
++ "pattern": "^hld_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "held_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "available_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "lease_expires_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++}
ddiff --git a/contracts/billing/credit-hold-extend-request.v1.json b/contracts/billing/credit-hold-extend-request.v1.json
nnew file mode 100644
iindex 00000000..139426ee
---- /dev/null
++++ b/contracts/billing/credit-hold-extend-request.v1.json
@@@ -0,0 +1,23 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold-extend-request.v1.json",
++ "title": "BillingCreditHoldExtendRequest",
++ "description": "billing.credit-hold-extend-request@1: body of POST /internal/credits/holds/:id/extend. Grows an active hold by add_nano_usd.",
++ "type": "object",
++ "required": [
++ "add_nano_usd"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "add_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++}
ddiff --git a/contracts/billing/credit-hold-release-result.v1.json b/contracts/billing/credit-hold-release-result.v1.json
nnew file mode 100644
iindex 00000000..f0ad5455
---- /dev/null
++++ b/contracts/billing/credit-hold-release-result.v1.json
@@@ -0,0 +1,28 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold-release-result.v1.json",
++ "title": "BillingCreditHoldReleaseResult",
++ "description": "billing.credit-hold-release-result@1: response from POST /internal/credits/holds/:id/release.",
++ "type": "object",
++ "required": [
++ "hold_id",
++ "released_nano_usd"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "hold_id": {
++ "type": "string",
++ "pattern": "^hld_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "released_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++}
ddiff --git a/contracts/billing/credit-hold-settle-request.v1.json b/contracts/billing/credit-hold-settle-request.v1.json
nnew file mode 100644
iindex 00000000..cd2c1f7f
---- /dev/null
++++ b/contracts/billing/credit-hold-settle-request.v1.json
@@@ -0,0 +1,145 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold-settle-request.v1.json",
++ "title": "BillingCreditHoldSettleRequest",
++ "description": "billing.credit-hold-settle-request@1: body of POST /internal/credits/holds/:id/settle. Idempotent: the same key and fingerprint return the same settlement.",
++ "type": "object",
++ "required": [
++ "idempotency_key",
++ "usage",
++ "retail",
++ "supply_cost_nano_usd",
++ "byok",
++ "attempts"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "idempotency_key": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9._:-]{8,128}$"
++ },
++ "usage": {
++ "type": "object",
++ "required": [
++ "input_tokens",
++ "cached_read_tokens",
++ "cache_write_tokens",
++ "output_tokens"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "input_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cached_read_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output_tokens": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "retail": {
++ "type": "object",
++ "required": [
++ "version",
++ "nano_usd_per_m",
++ "per_request_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "version": {
++ "type": "string"
++ },
++ "nano_usd_per_m": {
++ "type": "object",
++ "required": [
++ "input",
++ "cached_read",
++ "cache_write",
++ "output"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "input": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "cached_read": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "cache_write": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "output": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++ },
++ "per_request_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++ },
++ "supply_cost_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "byok": {
++ "type": "boolean"
++ },
++ "attempts": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "deployment_id",
++ "started_at",
++ "ended_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "deployment_id": {
++ "type": "string"
++ },
++ "provider_access": {
++ "type": "string",
++ "enum": [
++ "OWN_DEPLOYMENT",
++ "AUTHORIZED_WHOLESALE",
++ "CUSTOMER_BYOK",
++ "RESTRICTED",
++ "UNVERIFIED"
++ ]
++ },
++ "started_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "ended_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "error_code": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ }
++ }
++}
ddiff --git a/contracts/billing/credit-hold-settle-result.v1.json b/contracts/billing/credit-hold-settle-result.v1.json
nnew file mode 100644
iindex 00000000..bb6e6d59
---- /dev/null
++++ b/contracts/billing/credit-hold-settle-result.v1.json
@@@ -0,0 +1,37 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold-settle-result.v1.json",
++ "title": "BillingCreditHoldSettleResult",
++ "description": "billing.credit-hold-settle-result@1: response from POST /internal/credits/holds/:id/settle.",
++ "type": "object",
++ "required": [
++ "charge_nano_usd",
++ "released_nano_usd",
++ "journal_id"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "charge_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "released_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "journal_id": {
++ "type": "string",
++ "pattern": "^txn_"
++ },
++ "shortfall_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Usage beyond the hold and the free balance: owed, not collected (nano-USD, D1)."
++ }
++ }
++}
ddiff --git a/contracts/billing/credit-hold.v1.json b/contracts/billing/credit-hold.v1.json
nnew file mode 100644
iindex 00000000..e216cc87
---- /dev/null
++++ b/contracts/billing/credit-hold.v1.json
@@@ -0,0 +1,86 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold.v1.json",
++ "title": "BillingCreditHold",
++ "description": "billing.credit-hold@1: a reservation against an account's available inference credit. Holds are created before a request runs, may be extended while streaming, and are settled or released exactly once. Money fields are integer nano-USD, as strings.",
++ "type": "object",
++ "required": [
++ "hold_id",
++ "subject",
++ "project_id",
++ "held_nano_usd",
++ "available_nano_usd",
++ "lease_expires_at"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "hold_id": {
++ "type": "string",
++ "pattern": "^hld_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "ULID hold id."
++ },
++ "subject": {
++ "$ref": "../identity/subject-ref.v1.json"
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "credential_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^cred_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Developer credential the hold is scoped to, if any."
++ },
++ "held_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "available_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "max_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "rate_version": {
++ "type": "string",
++ "description": "Immutable retail rate-card version the hold was priced against."
++ },
++ "request_fingerprint": {
++ "type": "string",
++ "maxLength": 1024,
++ "description": "Opaque fingerprint used to dedupe the same idempotency key."
++ },
++ "lease_expires_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "status": {
++ "type": "string",
++ "enum": [
++ "active",
++ "extended",
++ "settled",
++ "released",
++ "expired"
++ ]
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{0,39}$",
++ "description": "Which metered service spends the credit (D9): ai, run, ... Defaults to ai."
++ }
++ }
++}
ddiff --git a/contracts/billing/credit-sandbox-grant-request.v1.json b/contracts/billing/credit-sandbox-grant-request.v1.json
nnew file mode 100644
iindex 00000000..01f3aa20
---- /dev/null
++++ b/contracts/billing/credit-sandbox-grant-request.v1.json
@@@ -0,0 +1,31 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-sandbox-grant-request.v1.json",
++ "title": "BillingCreditSandboxGrantRequest",
++ "description": "billing.credit-sandbox-grant-request@1: POST /internal/credits/sandbox/grants, synthetic credit for tests and staging. Billing answers only when BILLING_CREDITS_SANDBOX=1; the grant is a test transaction (D9).",
++ "type": "object",
++ "required": [
++ "subject",
++ "project_id",
++ "amount_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "$ref": "../identity/subject-ref.v1.json"
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "amount_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ },
++ "reason": {
++ "type": "string",
++ "maxLength": 500
++ }
++ }
++}
\\ No newline at end of file
ddiff --git a/contracts/billing/credit-sandbox-grant-result.v1.json b/contracts/billing/credit-sandbox-grant-result.v1.json
nnew file mode 100644
iindex 00000000..e2fa384b
---- /dev/null
++++ b/contracts/billing/credit-sandbox-grant-result.v1.json
@@@ -0,0 +1,29 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-sandbox-grant-result.v1.json",
++ "title": "BillingCreditSandboxGrantResult",
++ "description": "billing.credit-sandbox-grant-result@1: the grant and the balance after it.",
++ "type": "object",
++ "required": [
++ "grant_id",
++ "amount_nano_usd",
++ "balance_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "grant_id": {
++ "type": "string",
++ "pattern": "^igr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "amount_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ },
++ "balance_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ }
++}
\\ No newline at end of file
ddiff --git a/contracts/billing/credit-settlement.v1.json b/contracts/billing/credit-settlement.v1.json
nnew file mode 100644
iindex 00000000..fcdc5903
---- /dev/null
++++ b/contracts/billing/credit-settlement.v1.json
@@@ -0,0 +1,183 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-settlement.v1.json",
++ "title": "BillingCreditSettlement",
++ "description": "billing.credit-settlement@1: the final charge for one inference hold. Carries the measured usage, the retail rate version applied, the supply-side cost, whether the route was BYOK, and any attempts/fallbacks. Money fields are integer nano-USD, as strings.",
++ "type": "object",
++ "required": [
++ "settlement_id",
++ "hold_id",
++ "subject",
++ "project_id",
++ "charge_nano_usd",
++ "released_nano_usd",
++ "journal_id"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ },
++ "token-usage": {
++ "type": "object",
++ "required": [
++ "input_tokens",
++ "cached_read_tokens",
++ "cache_write_tokens",
++ "output_tokens"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "input_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cached_read_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output_tokens": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "rate-versioned": {
++ "type": "object",
++ "required": [
++ "version",
++ "nano_usd_per_m",
++ "per_request_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "version": {
++ "type": "string"
++ },
++ "nano_usd_per_m": {
++ "type": "object",
++ "required": [
++ "input",
++ "cached_read",
++ "cache_write",
++ "output"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "input": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "cached_read": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "cache_write": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "output": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++ },
++ "per_request_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++ }
++ },
++ "properties": {
++ "settlement_id": {
++ "type": "string",
++ "pattern": "^stl_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "hold_id": {
++ "type": "string",
++ "pattern": "^hld_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "subject": {
++ "$ref": "../identity/subject-ref.v1.json"
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "charge_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "released_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "journal_id": {
++ "type": "string",
++ "pattern": "^txn_"
++ },
++ "usage": {
++ "$ref": "#/$defs/token-usage"
++ },
++ "retail": {
++ "$ref": "#/$defs/rate-versioned"
++ },
++ "supply_cost_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "byok": {
++ "type": "boolean",
++ "description": "True when the caller supplied their own provider key; retail charge is 0."
++ },
++ "attempts": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "deployment_id",
++ "started_at",
++ "ended_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "deployment_id": {
++ "type": "string"
++ },
++ "provider_access": {
++ "type": "string",
++ "enum": [
++ "OWN_DEPLOYMENT",
++ "AUTHORIZED_WHOLESALE",
++ "CUSTOMER_BYOK",
++ "RESTRICTED",
++ "UNVERIFIED"
++ ]
++ },
++ "started_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "ended_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "error_code": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "shortfall_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Usage beyond the hold and the free balance: owed, not collected (nano-USD, D1)."
++ }
++ }
++}
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex cb339280..f39607ac 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -1450,13 +1450,58 @@
},
{
"id": "ai.model",
-- "version": "1.0.0",
++ "version": "1.1.0",
"owner": "ai",
"schema": "ai/model.v1.json",
"visibility": "first-party",
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "ai.model-artifact",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/model-artifact.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "ai.inference-deployment",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/inference-deployment.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "ai.route-explain",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/route-explain.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "ai.model-list",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/model-list.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "ai.model-read-result",
++ "version": "1.0.0",
++ "owner": "ai",
++ "schema": "ai/model-read-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "ai.provider-manage-request",
"version": "1.0.0",
@@@ -2385,6 +2430,78 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "billing.credit-hold",
++ "version": "1.0.0",
++ "owner": "billing",
++ "schema": "billing/credit-hold.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "billing.credit-settlement",
++ "version": "1.0.0",
++ "owner": "billing",
++ "schema": "billing/credit-settlement.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "billing.credit-hold-create-request",
++ "version": "1.0.0",
++ "owner": "billing",
++ "schema": "billing/credit-hold-create-request.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "billing.credit-hold-create-result",
++ "version": "1.0.0",
++ "owner": "billing",
++ "schema": "billing/credit-hold-create-result.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "billing.credit-hold-extend-request",
++ "version": "1.0.0",
++ "owner": "billing",
++ "schema": "billing/credit-hold-extend-request.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "billing.credit-hold-settle-request",
++ "version": "1.0.0",
++ "owner": "billing",
++ "schema": "billing/credit-hold-settle-request.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "billing.credit-hold-settle-result",
++ "version": "1.0.0",
++ "owner": "billing",
++ "schema": "billing/credit-hold-settle-result.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "billing.credit-hold-release-result",
++ "version": "1.0.0",
++ "owner": "billing",
++ "schema": "billing/credit-hold-release-result.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "deals.watch.matched",
"version": "1.0.0",
@@@ -6091,7 +6208,7 @@
},
{
"id": "platform.rate-card",
-- "version": "1.0.0",
++ "version": "1.1.0",
"owner": "network",
"schema": "platform/rate-card.v1.json",
"visibility": "first-party",
@@@ -8842,6 +8959,24 @@
"visibility": "first-party",
"compatibility": "backward",
"status": "active"
++ },
++ {
++ "id": "billing.credit-sandbox-grant-request",
++ "version": "1.0.0",
++ "owner": "billing",
++ "schema": "billing/credit-sandbox-grant-request.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "billing.credit-sandbox-grant-result",
++ "version": "1.0.0",
++ "owner": "billing",
++ "schema": "billing/credit-sandbox-grant-result.v1.json",
++ "visibility": "internal",
++ "compatibility": "backward",
++ "status": "active"
}
]
--}
++}
\\ No newline at end of file
ddiff --git a/contracts/platform/rate-card.v1.json b/contracts/platform/rate-card.v1.json
iindex c9998dbe..1d1c8a2f 100644
---- a/contracts/platform/rate-card.v1.json
++++ b/contracts/platform/rate-card.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/platform/rate-card.v1.json",
"title": "RateCard",
-- "description": "One provider price with its free allowance, in the provider's real billing unit, with effective dates and where it was checked (roadmap WS-Z3 task 5). Changed only by review; never written by an AI on its own.",
++ "description": "One provider price with its free allowance, in the provider's real billing unit, with effective dates and where it was checked (roadmap WS-Z3 task 5). v1.1 adds optional `side` (supply|retail), `version` and `effective_to`; existing documents stay valid. Changed only by review; never written by an AI on its own.",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -27,6 +27,18 @@
"adapter": {
"type": "string"
},
++ "side": {
++ "type": "string",
++ "enum": [
++ "supply",
++ "retail"
++ ],
++ "description": "Whether this card prices what OpenVibe pays (supply) or what the customer pays (retail)."
++ },
++ "version": {
++ "type": "string",
++ "description": "Immutable version of this card; settlements reference it so historical prices survive updates."
++ },
"metric": {
"type": "string",
"description": "e.g. queue-operation-64kb, request, gib-delivered, message, gb-month, class-a-op"
@@@ -58,6 +70,11 @@
"type": "string",
"format": "date"
},
++ "effective_to": {
++ "type": "string",
++ "format": "date",
++ "description": "Inclusive end date; null or absent means open-ended."
++ },
"effective_until": {
"type": "string",
"format": "date"
ddiff --git a/fixtures/ai.inference-deployment/invalid/bad-access.json b/fixtures/ai.inference-deployment/invalid/bad-access.json
nnew file mode 100644
iindex 00000000..faf71131
---- /dev/null
++++ b/fixtures/ai.inference-deployment/invalid/bad-access.json
@@@ -0,0 +1,7 @@
++{
++ "id": "dep-bad-access",
++ "artifact_id": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "region": "us-east",
++ "status": "ready",
++ "provider_access": "FREE"
++}
ddiff --git a/fixtures/ai.inference-deployment/invalid/bad-trust-tier.json b/fixtures/ai.inference-deployment/invalid/bad-trust-tier.json
nnew file mode 100644
iindex 00000000..b5bf6405
---- /dev/null
++++ b/fixtures/ai.inference-deployment/invalid/bad-trust-tier.json
@@@ -0,0 +1,7 @@
++{
++ "id": "dep-bad-trust",
++ "artifact_id": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "region": "us-east",
++ "trust_tier": "hostile",
++ "status": "ready"
++}
ddiff --git a/fixtures/ai.inference-deployment/invalid/missing-artifact.json b/fixtures/ai.inference-deployment/invalid/missing-artifact.json
nnew file mode 100644
iindex 00000000..8cf99889
---- /dev/null
++++ b/fixtures/ai.inference-deployment/invalid/missing-artifact.json
@@@ -0,0 +1,5 @@
++{
++ "id": "dep-no-artifact",
++ "region": "us-east",
++ "status": "ready"
++}
ddiff --git a/fixtures/ai.inference-deployment/valid/owned-gpu.json b/fixtures/ai.inference-deployment/valid/owned-gpu.json
nnew file mode 100644
iindex 00000000..7d5cb3f3
---- /dev/null
++++ b/fixtures/ai.inference-deployment/valid/owned-gpu.json
@@@ -0,0 +1,26 @@
++{
++ "id": "dep-qwen-9b-us-east-001",
++ "artifact_id": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "region": "us-east",
++ "trust_tier": "first_party",
++ "operator": "openvibe",
++ "host": "gpu-001.internal",
++ "endpoint": "http://127.0.0.1:4800/v1",
++ "status": "ready",
++ "loaded_at": "2026-11-03T00:00:00.000Z",
++ "limits": {
++ "max_concurrency": 16,
++ "context_tokens": 32768,
++ "max_output_tokens": 8192
++ },
++ "supply_rate_card_id": "rc-qwen-owned-001",
++ "provider_access": "OWN_DEPLOYMENT",
++ "commercial_rights": "OWN_DEPLOYMENT",
++ "privacy_policy": "zero_retention",
++ "health": {
++ "healthy": true,
++ "last_check_at": "2026-11-03T00:00:00.000Z",
++ "reason": null
++ },
++ "updated_at": "2026-11-03T00:00:00.000Z"
++}
ddiff --git a/fixtures/ai.inference-deployment/valid/wholesale.json b/fixtures/ai.inference-deployment/valid/wholesale.json
nnew file mode 100644
iindex 00000000..284c7977
---- /dev/null
++++ b/fixtures/ai.inference-deployment/valid/wholesale.json
@@@ -0,0 +1,25 @@
++{
++ "id": "dep-deepinfra-llama-8b",
++ "artifact_id": "meta-llama/llama_3.1_8b_instruct:fp16@bca2580",
++ "region": "us-west",
++ "trust_tier": "approved_wholesale",
++ "operator": "deepinfra",
++ "endpoint": "https://api.deepinfra.com/v1/openai",
++ "status": "ready",
++ "loaded_at": "2026-11-03T00:00:00.000Z",
++ "limits": {
++ "max_concurrency": 100,
++ "context_tokens": 131072,
++ "max_output_tokens": 4096
++ },
++ "supply_rate_card_id": "rc-deepinfra-llama-8b-001",
++ "provider_access": "AUTHORIZED_WHOLESALE",
++ "commercial_rights": "AUTHORIZED_WHOLESALE",
++ "privacy_policy": null,
++ "health": {
++ "healthy": true,
++ "last_check_at": "2026-11-03T00:00:00.000Z",
++ "reason": null
++ },
++ "updated_at": "2026-11-03T00:00:00.000Z"
++}
ddiff --git a/fixtures/ai.model-artifact/invalid/bad-id.json b/fixtures/ai.model-artifact/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..c0d9eafb
---- /dev/null
++++ b/fixtures/ai.model-artifact/invalid/bad-id.json
@@@ -0,0 +1,9 @@
++{
++ "id": "qwen3-9b@3f2a1c9",
++ "family": "qwen3",
++ "variant": "original",
++ "quant": "q4_k_m",
++ "revision": "3f2a1c9",
++ "sha256": "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899",
++ "license": "apache-2.0"
++}
ddiff --git a/fixtures/ai.model-artifact/invalid/invalid-variant.json b/fixtures/ai.model-artifact/invalid/invalid-variant.json
nnew file mode 100644
iindex 00000000..358210ca
---- /dev/null
++++ b/fixtures/ai.model-artifact/invalid/invalid-variant.json
@@@ -0,0 +1,9 @@
++{
++ "id": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "family": "qwen3",
++ "variant": "censored",
++ "quant": "q4_k_m",
++ "revision": "3f2a1c9",
++ "sha256": "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899",
++ "license": "apache-2.0"
++}
ddiff --git a/fixtures/ai.model-artifact/invalid/missing-sha256.json b/fixtures/ai.model-artifact/invalid/missing-sha256.json
nnew file mode 100644
iindex 00000000..a564cfca
---- /dev/null
++++ b/fixtures/ai.model-artifact/invalid/missing-sha256.json
@@@ -0,0 +1,8 @@
++{
++ "id": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "family": "qwen3",
++ "variant": "abliterated",
++ "quant": "q4_k_m",
++ "revision": "3f2a1c9",
++ "license": "apache-2.0"
++}
ddiff --git a/fixtures/ai.model-artifact/valid/llama.json b/fixtures/ai.model-artifact/valid/llama.json
nnew file mode 100644
iindex 00000000..6b2143a1
---- /dev/null
++++ b/fixtures/ai.model-artifact/valid/llama.json
@@@ -0,0 +1,13 @@
++{
++ "id": "meta-llama/llama_3.1_8b_instruct:fp16@bca2580",
++ "family": "llama3.1",
++ "variant": "original",
++ "quant": "fp16",
++ "revision": "bca2580",
++ "sha256": "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
++ "tokenizer_revision": "bca2580",
++ "license": "llama3.1",
++ "source_url": "https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct",
++ "context": 131072,
++ "created_at": "2026-11-03T00:00:00.000Z"
++}
ddiff --git a/fixtures/ai.model-artifact/valid/qwen.json b/fixtures/ai.model-artifact/valid/qwen.json
nnew file mode 100644
iindex 00000000..47b4428c
---- /dev/null
++++ b/fixtures/ai.model-artifact/valid/qwen.json
@@@ -0,0 +1,13 @@
++{
++ "id": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "family": "qwen3",
++ "variant": "abliterated",
++ "quant": "q4_k_m",
++ "revision": "3f2a1c9",
++ "sha256": "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899",
++ "tokenizer_revision": "3f2a1c9",
++ "license": "apache-2.0",
++ "source_url": "https://huggingface.co/qwen/qwen3-9b-abliterated-gguf",
++ "context": 32768,
++ "created_at": "2026-11-03T00:00:00.000Z"
++}
ddiff --git a/fixtures/ai.model-list/invalid/bad-model.json b/fixtures/ai.model-list/invalid/bad-model.json
nnew file mode 100644
iindex 00000000..65983881
---- /dev/null
++++ b/fixtures/ai.model-list/invalid/bad-model.json
@@@ -0,0 +1,15 @@
++{
++ "models": [
++ {
++ "provider_key": "qwen",
++ "model_key": "qwen3-9b-abliterated",
++ "type": "chat",
++ "status": "active",
++ "cost": { "in_per_mtok": 3, "out_per_mtok": 15, "cached_per_mtok": 0.3 },
++ "supports": { "json": true, "tools": true, "streaming": true, "vision": false },
++ "retail_rate": { "input_nano_usd_per_m": "3000.5" }
++ }
++ ],
++ "count": 1,
++ "cursor": null
++}
ddiff --git a/fixtures/ai.model-list/invalid/missing-required.json b/fixtures/ai.model-list/invalid/missing-required.json
nnew file mode 100644
iindex 00000000..f53c5a06
---- /dev/null
++++ b/fixtures/ai.model-list/invalid/missing-required.json
@@@ -0,0 +1,13 @@
++{
++ "models": [
++ {
++ "provider_key": "qwen",
++ "type": "chat",
++ "status": "active",
++ "cost": { "in_per_mtok": 3, "out_per_mtok": 15, "cached_per_mtok": 0.3 },
++ "supports": { "json": true, "tools": true, "streaming": true, "vision": false }
++ }
++ ],
++ "count": 1,
++ "cursor": null
++}
ddiff --git a/fixtures/ai.model-list/invalid/negative-count.json b/fixtures/ai.model-list/invalid/negative-count.json
nnew file mode 100644
iindex 00000000..472a0564
---- /dev/null
++++ b/fixtures/ai.model-list/invalid/negative-count.json
@@@ -0,0 +1,5 @@
++{
++ "models": [],
++ "count": -1,
++ "cursor": null
++}
ddiff --git a/fixtures/ai.model-list/valid/empty.json b/fixtures/ai.model-list/valid/empty.json
nnew file mode 100644
iindex 00000000..c0877090
---- /dev/null
++++ b/fixtures/ai.model-list/valid/empty.json
@@@ -0,0 +1,5 @@
++{
++ "models": [],
++ "count": 0,
++ "cursor": null
++}
ddiff --git a/fixtures/ai.model-list/valid/two-models.json b/fixtures/ai.model-list/valid/two-models.json
nnew file mode 100644
iindex 00000000..ee743d3c
---- /dev/null
++++ b/fixtures/ai.model-list/valid/two-models.json
@@@ -0,0 +1,60 @@
++{
++ "models": [
++ {
++ "provider_key": "qwen",
++ "model_key": "qwen3-9b-abliterated",
++ "type": "chat",
++ "status": "active",
++ "cost": { "in_per_mtok": 3, "out_per_mtok": 15, "cached_per_mtok": 0.3 },
++ "supports": { "json": true, "tools": true, "streaming": true, "vision": false },
++ "id": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "family": "qwen3",
++ "variant": "abliterated",
++ "quant": "q4_k_m",
++ "revision": "3f2a1c9",
++ "sha256": "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899",
++ "license": "apache-2.0",
++ "context": 32768,
++ "capabilities": { "tools": true, "json": true, "vision": false, "stream": true },
++ "modalities": ["text"],
++ "retail_rate": {
++ "version": "rr-2026-11-03-001",
++ "input_nano_usd_per_m": "3000",
++ "cached_read_nano_usd_per_m": "300",
++ "cache_write_nano_usd_per_m": "3000",
++ "output_nano_usd_per_m": "15000",
++ "per_request_nano_usd": "0"
++ },
++ "availability": "available"
++ },
++ {
++ "provider_key": "meta-llama",
++ "model_key": "llama_3.1_8b_instruct",
++ "type": "chat",
++ "status": "active",
++ "cost": { "in_per_mtok": 2, "out_per_mtok": 10, "cached_per_mtok": 0.2 },
++ "supports": { "json": true, "tools": true, "streaming": true, "vision": false },
++ "id": "meta-llama/llama_3.1_8b_instruct:fp16@bca2580",
++ "family": "llama3.1",
++ "variant": "original",
++ "quant": "fp16",
++ "revision": "bca2580",
++ "sha256": "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff",
++ "license": "llama3.1",
++ "context": 131072,
++ "capabilities": { "tools": true, "json": true, "vision": false, "stream": true },
++ "modalities": ["text"],
++ "retail_rate": {
++ "version": "rr-2026-11-03-002",
++ "input_nano_usd_per_m": "2000",
++ "cached_read_nano_usd_per_m": "200",
++ "cache_write_nano_usd_per_m": "2000",
++ "output_nano_usd_per_m": "10000",
++ "per_request_nano_usd": "0"
++ },
++ "availability": "available"
++ }
++ ],
++ "count": 2,
++ "cursor": null
++}
ddiff --git a/fixtures/ai.model-read-result/invalid/bad-list.json b/fixtures/ai.model-read-result/invalid/bad-list.json
nnew file mode 100644
iindex 00000000..57ef0c33
---- /dev/null
++++ b/fixtures/ai.model-read-result/invalid/bad-list.json
@@@ -0,0 +1,15 @@
++{
++ "models": [
++ {
++ "provider_key": "qwen",
++ "model_key": "qwen3-9b-abliterated",
++ "type": "chat",
++ "status": "active",
++ "cost": { "in_per_mtok": 3, "out_per_mtok": 15, "cached_per_mtok": 0.3 },
++ "supports": { "json": true, "tools": true, "streaming": true, "vision": false },
++ "retail_rate": { "input_nano_usd_per_m": 3000 }
++ }
++ ],
++ "count": 1,
++ "cursor": null
++}
ddiff --git a/fixtures/ai.model-read-result/invalid/missing-required.json b/fixtures/ai.model-read-result/invalid/missing-required.json
nnew file mode 100644
iindex 00000000..847bd0c7
---- /dev/null
++++ b/fixtures/ai.model-read-result/invalid/missing-required.json
@@@ -0,0 +1,8 @@
++{
++ "provider_key": "qwen",
++ "model_key": "qwen3-9b-abliterated",
++ "type": "chat",
++ "status": "active",
++ "supports": { "json": true, "tools": true, "streaming": true, "vision": false },
++ "id": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9"
++}
ddiff --git a/fixtures/ai.model-read-result/invalid/neither.json b/fixtures/ai.model-read-result/invalid/neither.json
nnew file mode 100644
iindex 00000000..d2bd3f92
---- /dev/null
++++ b/fixtures/ai.model-read-result/invalid/neither.json
@@@ -0,0 +1,3 @@
++{
++ "cursor": null
++}
ddiff --git a/fixtures/ai.model-read-result/valid/list.json b/fixtures/ai.model-read-result/valid/list.json
nnew file mode 100644
iindex 00000000..ed4548a5
---- /dev/null
++++ b/fixtures/ai.model-read-result/valid/list.json
@@@ -0,0 +1,15 @@
++{
++ "models": [
++ {
++ "provider_key": "qwen",
++ "model_key": "qwen3-9b-abliterated",
++ "type": "chat",
++ "status": "active",
++ "cost": { "in_per_mtok": 3, "out_per_mtok": 15, "cached_per_mtok": 0.3 },
++ "supports": { "json": true, "tools": true, "streaming": true, "vision": false },
++ "id": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9"
++ }
++ ],
++ "count": 1,
++ "cursor": null
++}
ddiff --git a/fixtures/ai.model-read-result/valid/single.json b/fixtures/ai.model-read-result/valid/single.json
nnew file mode 100644
iindex 00000000..218d52c7
---- /dev/null
++++ b/fixtures/ai.model-read-result/valid/single.json
@@@ -0,0 +1,10 @@
++{
++ "provider_key": "qwen",
++ "model_key": "qwen3-9b-abliterated",
++ "type": "chat",
++ "status": "active",
++ "cost": { "in_per_mtok": 3, "out_per_mtok": 15, "cached_per_mtok": 0.3 },
++ "supports": { "json": true, "tools": true, "streaming": true, "vision": false },
++ "id": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "availability": "available"
++}
ddiff --git a/fixtures/ai.model/invalid/invalid-variant.json b/fixtures/ai.model/invalid/invalid-variant.json
nnew file mode 100644
iindex 00000000..74ae0408
---- /dev/null
++++ b/fixtures/ai.model/invalid/invalid-variant.json
@@@ -0,0 +1,10 @@
++{
++ "provider_key": "qwen",
++ "model_key": "qwen3-9b",
++ "type": "chat",
++ "status": "active",
++ "cost": { "in_per_mtok": 3, "out_per_mtok": 15, "cached_per_mtok": 0.3 },
++ "supports": { "json": true, "tools": true, "streaming": true, "vision": false },
++ "id": "qwen/qwen3-9b@3f2a1c9",
++ "variant": "tuned"
++}
ddiff --git a/fixtures/ai.model/invalid/money-float.json b/fixtures/ai.model/invalid/money-float.json
nnew file mode 100644
iindex 00000000..59293108
---- /dev/null
++++ b/fixtures/ai.model/invalid/money-float.json
@@@ -0,0 +1,14 @@
++{
++ "provider_key": "qwen",
++ "model_key": "qwen3-9b",
++ "type": "chat",
++ "status": "active",
++ "cost": { "in_per_mtok": 3, "out_per_mtok": 15, "cached_per_mtok": 0.3 },
++ "supports": { "json": true, "tools": true, "streaming": true, "vision": false },
++ "id": "qwen/qwen3-9b@3f2a1c9",
++ "variant": "original",
++ "retail_rate": {
++ "version": "v1",
++ "input_nano_usd_per_m": "3000.5"
++ }
++}
ddiff --git a/fixtures/ai.model/invalid/money-wrong-type.json b/fixtures/ai.model/invalid/money-wrong-type.json
nnew file mode 100644
iindex 00000000..d8dd48ac
---- /dev/null
++++ b/fixtures/ai.model/invalid/money-wrong-type.json
@@@ -0,0 +1,14 @@
++{
++ "provider_key": "qwen",
++ "model_key": "qwen3-9b",
++ "type": "chat",
++ "status": "active",
++ "cost": { "in_per_mtok": 3, "out_per_mtok": 15, "cached_per_mtok": 0.3 },
++ "supports": { "json": true, "tools": true, "streaming": true, "vision": false },
++ "id": "qwen/qwen3-9b@3f2a1c9",
++ "variant": "original",
++ "retail_rate": {
++ "version": "v1",
++ "input_nano_usd_per_m": 3000
++ }
++}
ddiff --git a/fixtures/ai.model/valid/qwen-catalog.json b/fixtures/ai.model/valid/qwen-catalog.json
nnew file mode 100644
iindex 00000000..e7ed0a85
---- /dev/null
++++ b/fixtures/ai.model/valid/qwen-catalog.json
@@@ -0,0 +1,48 @@
++{
++ "provider_key": "qwen",
++ "model_key": "qwen3-9b-abliterated",
++ "display_name": "Qwen3 9B Abliterated Q4_K_M",
++ "type": "chat",
++ "status": "active",
++ "context_window": 32768,
++ "max_output": 8192,
++ "cost": {
++ "in_per_mtok": 0.000003,
++ "out_per_mtok": 0.000015,
++ "cached_per_mtok": 0.0000003
++ },
++ "supports": {
++ "json": true,
++ "tools": true,
++ "streaming": true,
++ "vision": false
++ },
++ "id": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "aliases": ["openvibe/economy", "openvibe/balanced"],
++ "family": "qwen3",
++ "variant": "abliterated",
++ "quant": "q4_k_m",
++ "revision": "3f2a1c9",
++ "sha256": "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899",
++ "license": "apache-2.0",
++ "context": 32768,
++ "capabilities": {
++ "tools": true,
++ "json": true,
++ "vision": false,
++ "stream": true
++ },
++ "modalities": ["text"],
++ "retail_rate": {
++ "version": "rr-2026-11-03-001",
++ "input_nano_usd_per_m": "3000",
++ "cached_read_nano_usd_per_m": "300",
++ "cache_write_nano_usd_per_m": "3000",
++ "output_nano_usd_per_m": "15000",
++ "per_request_nano_usd": "0"
++ },
++ "availability": "available",
++ "metadata": {},
++ "created_at": "2026-11-03T00:00:00.000Z",
++ "updated_at": "2026-11-03T00:00:00.000Z"
++}
ddiff --git a/fixtures/ai.route-explain/invalid/bad-objective.json b/fixtures/ai.route-explain/invalid/bad-objective.json
nnew file mode 100644
iindex 00000000..a553f63c
---- /dev/null
++++ b/fixtures/ai.route-explain/invalid/bad-objective.json
@@@ -0,0 +1,6 @@
++{
++ "chosen": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "rejected": [],
++ "objective": "cheap",
++ "estimate": { "charge_nano_usd": "1000" }
++}
ddiff --git a/fixtures/ai.route-explain/invalid/float-money.json b/fixtures/ai.route-explain/invalid/float-money.json
nnew file mode 100644
iindex 00000000..52a1f730
---- /dev/null
++++ b/fixtures/ai.route-explain/invalid/float-money.json
@@@ -0,0 +1,6 @@
++{
++ "chosen": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "rejected": [],
++ "objective": "cheapest",
++ "estimate": { "charge_nano_usd": "12.34" }
++}
ddiff --git a/fixtures/ai.route-explain/invalid/missing-objective.json b/fixtures/ai.route-explain/invalid/missing-objective.json
nnew file mode 100644
iindex 00000000..da4f75c5
---- /dev/null
++++ b/fixtures/ai.route-explain/invalid/missing-objective.json
@@@ -0,0 +1,5 @@
++{
++ "chosen": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "rejected": [],
++ "estimate": { "charge_nano_usd": "1000" }
++}
ddiff --git a/fixtures/ai.route-explain/valid/balanced-fallback.json b/fixtures/ai.route-explain/valid/balanced-fallback.json
nnew file mode 100644
iindex 00000000..3e3b392d
---- /dev/null
++++ b/fixtures/ai.route-explain/valid/balanced-fallback.json
@@@ -0,0 +1,17 @@
++{
++ "chosen": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "rejected": [
++ { "id": "openvibe/fast", "reason": "latency ceiling exceeded" },
++ { "id": "meta-llama/llama_3.1_8b_instruct:fp16@bca2580", "reason": "higher expected cost per success" }
++ ],
++ "objective": "balanced",
++ "estimate": {
++ "charge_nano_usd": "12000",
++ "input_tokens": 800,
++ "cached_read_tokens": 0,
++ "cache_write_tokens": 0,
++ "output_tokens": 400
++ },
++ "policy": "balanced",
++ "fallback_reason": "first choice above latency ceiling"
++}
ddiff --git a/fixtures/ai.route-explain/valid/exact-choice.json b/fixtures/ai.route-explain/valid/exact-choice.json
nnew file mode 100644
iindex 00000000..6468c461
---- /dev/null
++++ b/fixtures/ai.route-explain/valid/exact-choice.json
@@@ -0,0 +1,23 @@
++{
++ "chosen": "qwen/qwen3-9b-abliterated:q4_k_m@3f2a1c9",
++ "rejected": [
++ { "id": "meta-llama/llama_3.1_8b_instruct:fp16@bca2580", "reason": "exact model requested" }
++ ],
++ "objective": "exact",
++ "estimate": {
++ "charge_nano_usd": "15000",
++ "input_tokens": 1000,
++ "cached_read_tokens": 0,
++ "cache_write_tokens": 0,
++ "output_tokens": 500
++ },
++ "actual": {
++ "charge_nano_usd": "15000",
++ "input_tokens": 1000,
++ "cached_read_tokens": 0,
++ "cache_write_tokens": 0,
++ "output_tokens": 500
++ },
++ "policy": "pin-exact",
++ "fallback_reason": null
++}
ddiff --git a/fixtures/billing.credit-hold-create-request/invalid/bad-subject.json b/fixtures/billing.credit-hold-create-request/invalid/bad-subject.json
nnew file mode 100644
iindex 00000000..5bf1a454
---- /dev/null
++++ b/fixtures/billing.credit-hold-create-request/invalid/bad-subject.json
@@@ -0,0 +1,8 @@
++{
++ "idempotency_key": "hold-key-001",
++ "subject": { "type": "person", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "max_nano_usd": "100000000",
++ "rate_version": "rr-2026-11-03-001",
++ "request_fingerprint": "fp-chat-001"
++}
ddiff --git a/fixtures/billing.credit-hold-create-request/invalid/missing-key.json b/fixtures/billing.credit-hold-create-request/invalid/missing-key.json
nnew file mode 100644
iindex 00000000..03207be0
---- /dev/null
++++ b/fixtures/billing.credit-hold-create-request/invalid/missing-key.json
@@@ -0,0 +1,7 @@
++{
++ "subject": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "max_nano_usd": "100000000",
++ "rate_version": "rr-2026-11-03-001",
++ "request_fingerprint": "fp-chat-001"
++}
ddiff --git a/fixtures/billing.credit-hold-create-request/invalid/money-number.json b/fixtures/billing.credit-hold-create-request/invalid/money-number.json
nnew file mode 100644
iindex 00000000..cf7966aa
---- /dev/null
++++ b/fixtures/billing.credit-hold-create-request/invalid/money-number.json
@@@ -0,0 +1,8 @@
++{
++ "idempotency_key": "hold-key-001",
++ "subject": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "max_nano_usd": 100000000,
++ "rate_version": "rr-2026-11-03-001",
++ "request_fingerprint": "fp-chat-001"
++}
ddiff --git a/fixtures/billing.credit-hold-create-request/valid/minimal.json b/fixtures/billing.credit-hold-create-request/valid/minimal.json
nnew file mode 100644
iindex 00000000..a4e17b12
---- /dev/null
++++ b/fixtures/billing.credit-hold-create-request/valid/minimal.json
@@@ -0,0 +1,8 @@
++{
++ "idempotency_key": "hold-key-001",
++ "subject": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "max_nano_usd": "100000000",
++ "rate_version": "rr-2026-11-03-001",
++ "request_fingerprint": "fp-chat-001"
++}
ddiff --git a/fixtures/billing.credit-hold-create-request/valid/with-credential.json b/fixtures/billing.credit-hold-create-request/valid/with-credential.json
nnew file mode 100644
iindex 00000000..5f55c938
---- /dev/null
++++ b/fixtures/billing.credit-hold-create-request/valid/with-credential.json
@@@ -0,0 +1,9 @@
++{
++ "idempotency_key": "hold-key-002",
++ "subject": { "type": "app", "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "credential_id": "cred_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "max_nano_usd": "50000000",
++ "rate_version": "rr-2026-11-03-001",
++ "request_fingerprint": "fp-chat-002"
++}
ddiff --git a/fixtures/billing.credit-hold-create-result/invalid/bad-id.json b/fixtures/billing.credit-hold-create-result/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..a22bda06
---- /dev/null
++++ b/fixtures/billing.credit-hold-create-result/invalid/bad-id.json
@@@ -0,0 +1,6 @@
++{
++ "hold_id": "hold_123",
++ "held_nano_usd": "100000000",
++ "available_nano_usd": "100000000",
++ "lease_expires_at": "2026-11-03T01:00:00.000Z"
++}
ddiff --git a/fixtures/billing.credit-hold-create-result/invalid/missing-hold.json b/fixtures/billing.credit-hold-create-result/invalid/missing-hold.json
nnew file mode 100644
iindex 00000000..4d55da4c
---- /dev/null
++++ b/fixtures/billing.credit-hold-create-result/invalid/missing-hold.json
@@@ -0,0 +1,5 @@
++{
++ "held_nano_usd": "100000000",
++ "available_nano_usd": "100000000",
++ "lease_expires_at": "2026-11-03T01:00:00.000Z"
++}
ddiff --git a/fixtures/billing.credit-hold-create-result/invalid/money-float.json b/fixtures/billing.credit-hold-create-result/invalid/money-float.json
nnew file mode 100644
iindex 00000000..fa5ceed9
---- /dev/null
++++ b/fixtures/billing.credit-hold-create-result/invalid/money-float.json
@@@ -0,0 +1,6 @@
++{
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "held_nano_usd": "100000000.5",
++ "available_nano_usd": "100000000",
++ "lease_expires_at": "2026-11-03T01:00:00.000Z"
++}
ddiff --git a/fixtures/billing.credit-hold-create-result/valid/full.json b/fixtures/billing.credit-hold-create-result/valid/full.json
nnew file mode 100644
iindex 00000000..3653152c
---- /dev/null
++++ b/fixtures/billing.credit-hold-create-result/valid/full.json
@@@ -0,0 +1,6 @@
++{
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "held_nano_usd": "100000000",
++ "available_nano_usd": "100000000",
++ "lease_expires_at": "2026-11-03T01:00:00.000Z"
++}
ddiff --git a/fixtures/billing.credit-hold-create-result/valid/partial.json b/fixtures/billing.credit-hold-create-result/valid/partial.json
nnew file mode 100644
iindex 00000000..35d04d4e
---- /dev/null
++++ b/fixtures/billing.credit-hold-create-result/valid/partial.json
@@@ -0,0 +1,6 @@
++{
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "held_nano_usd": "50000000",
++ "available_nano_usd": "25000000",
++ "lease_expires_at": "2026-11-03T01:00:00.000Z"
++}
ddiff --git a/fixtures/billing.credit-hold-extend-request/invalid/float.json b/fixtures/billing.credit-hold-extend-request/invalid/float.json
nnew file mode 100644
iindex 00000000..e9b59da4
---- /dev/null
++++ b/fixtures/billing.credit-hold-extend-request/invalid/float.json
@@@ -0,0 +1,3 @@
++{
++ "add_nano_usd": "1000.5"
++}
ddiff --git a/fixtures/billing.credit-hold-extend-request/invalid/missing.json b/fixtures/billing.credit-hold-extend-request/invalid/missing.json
nnew file mode 100644
iindex 00000000..0967ef42
---- /dev/null
++++ b/fixtures/billing.credit-hold-extend-request/invalid/missing.json
@@@ -0,0 +1 @@
++{}
ddiff --git a/fixtures/billing.credit-hold-extend-request/invalid/negative.json b/fixtures/billing.credit-hold-extend-request/invalid/negative.json
nnew file mode 100644
iindex 00000000..3c30cb9a
---- /dev/null
++++ b/fixtures/billing.credit-hold-extend-request/invalid/negative.json
@@@ -0,0 +1,3 @@
++{
++ "add_nano_usd": "-1000"
++}
ddiff --git a/fixtures/billing.credit-hold-extend-request/valid/extend.json b/fixtures/billing.credit-hold-extend-request/valid/extend.json
nnew file mode 100644
iindex 00000000..15e6b5ad
---- /dev/null
++++ b/fixtures/billing.credit-hold-extend-request/valid/extend.json
@@@ -0,0 +1,3 @@
++{
++ "add_nano_usd": "25000000"
++}
ddiff --git a/fixtures/billing.credit-hold-extend-request/valid/small.json b/fixtures/billing.credit-hold-extend-request/valid/small.json
nnew file mode 100644
iindex 00000000..e9b48734
---- /dev/null
++++ b/fixtures/billing.credit-hold-extend-request/valid/small.json
@@@ -0,0 +1,3 @@
++{
++ "add_nano_usd": "1"
++}
ddiff --git a/fixtures/billing.credit-hold-release-result/invalid/bad-id.json b/fixtures/billing.credit-hold-release-result/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..bd6310a8
---- /dev/null
++++ b/fixtures/billing.credit-hold-release-result/invalid/bad-id.json
@@@ -0,0 +1,4 @@
++{
++ "hold_id": "hold_123",
++ "released_nano_usd": "100000000"
++}
ddiff --git a/fixtures/billing.credit-hold-release-result/invalid/missing-id.json b/fixtures/billing.credit-hold-release-result/invalid/missing-id.json
nnew file mode 100644
iindex 00000000..15201fad
---- /dev/null
++++ b/fixtures/billing.credit-hold-release-result/invalid/missing-id.json
@@@ -0,0 +1,3 @@
++{
++ "released_nano_usd": "100000000"
++}
ddiff --git a/fixtures/billing.credit-hold-release-result/invalid/money-float.json b/fixtures/billing.credit-hold-release-result/invalid/money-float.json
nnew file mode 100644
iindex 00000000..cac7aea5
---- /dev/null
++++ b/fixtures/billing.credit-hold-release-result/invalid/money-float.json
@@@ -0,0 +1,4 @@
++{
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "released_nano_usd": "100000000.5"
++}
ddiff --git a/fixtures/billing.credit-hold-release-result/valid/full.json b/fixtures/billing.credit-hold-release-result/valid/full.json
nnew file mode 100644
iindex 00000000..ea33ce57
---- /dev/null
++++ b/fixtures/billing.credit-hold-release-result/valid/full.json
@@@ -0,0 +1,4 @@
++{
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "released_nano_usd": "100000000"
++}
ddiff --git a/fixtures/billing.credit-hold-release-result/valid/partial.json b/fixtures/billing.credit-hold-release-result/valid/partial.json
nnew file mode 100644
iindex 00000000..045963ed
---- /dev/null
++++ b/fixtures/billing.credit-hold-release-result/valid/partial.json
@@@ -0,0 +1,4 @@
++{
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "released_nano_usd": "82000000"
++}
ddiff --git a/fixtures/billing.credit-hold-settle-request/invalid/missing-key.json b/fixtures/billing.credit-hold-settle-request/invalid/missing-key.json
nnew file mode 100644
iindex 00000000..3688b96c
---- /dev/null
++++ b/fixtures/billing.credit-hold-settle-request/invalid/missing-key.json
@@@ -0,0 +1,21 @@
++{
++ "usage": {
++ "input_tokens": 1000,
++ "cached_read_tokens": 0,
++ "cache_write_tokens": 0,
++ "output_tokens": 500
++ },
++ "retail": {
++ "version": "rr-2026-11-03-001",
++ "nano_usd_per_m": {
++ "input": "3000",
++ "cached_read": "300",
++ "cache_write": "3000",
++ "output": "15000"
++ },
++ "per_request_nano_usd": "0"
++ },
++ "supply_cost_nano_usd": "9000",
++ "byok": false,
++ "attempts": []
++}
ddiff --git a/fixtures/billing.credit-hold-settle-request/invalid/missing-usage.json b/fixtures/billing.credit-hold-settle-request/invalid/missing-usage.json
nnew file mode 100644
iindex 00000000..11aa5e14
---- /dev/null
++++ b/fixtures/billing.credit-hold-settle-request/invalid/missing-usage.json
@@@ -0,0 +1,16 @@
++{
++ "idempotency_key": "settle-key-001",
++ "retail": {
++ "version": "rr-2026-11-03-001",
++ "nano_usd_per_m": {
++ "input": "3000",
++ "cached_read": "300",
++ "cache_write": "3000",
++ "output": "15000"
++ },
++ "per_request_nano_usd": "0"
++ },
++ "supply_cost_nano_usd": "9000",
++ "byok": false,
++ "attempts": []
++}
ddiff --git a/fixtures/billing.credit-hold-settle-request/invalid/money-number.json b/fixtures/billing.credit-hold-settle-request/invalid/money-number.json
nnew file mode 100644
iindex 00000000..d370f266
---- /dev/null
++++ b/fixtures/billing.credit-hold-settle-request/invalid/money-number.json
@@@ -0,0 +1,22 @@
++{
++ "idempotency_key": "settle-key-001",
++ "usage": {
++ "input_tokens": 1000,
++ "cached_read_tokens": 0,
++ "cache_write_tokens": 0,
++ "output_tokens": 500
++ },
++ "retail": {
++ "version": "rr-2026-11-03-001",
++ "nano_usd_per_m": {
++ "input": "3000",
++ "cached_read": "300",
++ "cache_write": "3000",
++ "output": 15000
++ },
++ "per_request_nano_usd": "0"
++ },
++ "supply_cost_nano_usd": "9000",
++ "byok": false,
++ "attempts": []
++}
ddiff --git a/fixtures/billing.credit-hold-settle-request/valid/byok.json b/fixtures/billing.credit-hold-settle-request/valid/byok.json
nnew file mode 100644
iindex 00000000..dd05359b
---- /dev/null
++++ b/fixtures/billing.credit-hold-settle-request/valid/byok.json
@@@ -0,0 +1,30 @@
++{
++ "idempotency_key": "settle-key-002",
++ "usage": {
++ "input_tokens": 500,
++ "cached_read_tokens": 0,
++ "cache_write_tokens": 0,
++ "output_tokens": 200
++ },
++ "retail": {
++ "version": "rr-2026-11-03-001",
++ "nano_usd_per_m": {
++ "input": "0",
++ "cached_read": "0",
++ "cache_write": "0",
++ "output": "0"
++ },
++ "per_request_nano_usd": "0"
++ },
++ "supply_cost_nano_usd": "0",
++ "byok": true,
++ "attempts": [
++ {
++ "deployment_id": "dep-owner-byok",
++ "provider_access": "CUSTOMER_BYOK",
++ "started_at": "2026-11-03T00:00:01.000Z",
++ "ended_at": "2026-11-03T00:00:04.000Z",
++ "error_code": null
++ }
++ ]
++}
ddiff --git a/fixtures/billing.credit-hold-settle-request/valid/full.json b/fixtures/billing.credit-hold-settle-request/valid/full.json
nnew file mode 100644
iindex 00000000..4f71bc94
---- /dev/null
++++ b/fixtures/billing.credit-hold-settle-request/valid/full.json
@@@ -0,0 +1,30 @@
++{
++ "idempotency_key": "settle-key-001",
++ "usage": {
++ "input_tokens": 1000,
++ "cached_read_tokens": 0,
++ "cache_write_tokens": 0,
++ "output_tokens": 500
++ },
++ "retail": {
++ "version": "rr-2026-11-03-001",
++ "nano_usd_per_m": {
++ "input": "3000",
++ "cached_read": "300",
++ "cache_write": "3000",
++ "output": "15000"
++ },
++ "per_request_nano_usd": "0"
++ },
++ "supply_cost_nano_usd": "9000",
++ "byok": false,
++ "attempts": [
++ {
++ "deployment_id": "dep-qwen-9b-us-east-001",
++ "provider_access": "OWN_DEPLOYMENT",
++ "started_at": "2026-11-03T00:00:01.000Z",
++ "ended_at": "2026-11-03T00:00:05.000Z",
++ "error_code": null
++ }
++ ]
++}
ddiff --git a/fixtures/billing.credit-hold-settle-result/invalid/missing-journal.json b/fixtures/billing.credit-hold-settle-result/invalid/missing-journal.json
nnew file mode 100644
iindex 00000000..3462543f
---- /dev/null
++++ b/fixtures/billing.credit-hold-settle-result/invalid/missing-journal.json
@@@ -0,0 +1,4 @@
++{
++ "charge_nano_usd": "18000",
++ "released_nano_usd": "82000000"
++}
ddiff --git a/fixtures/billing.credit-hold-settle-result/invalid/money-float.json b/fixtures/billing.credit-hold-settle-result/invalid/money-float.json
nnew file mode 100644
iindex 00000000..5ca193da
---- /dev/null
++++ b/fixtures/billing.credit-hold-settle-result/invalid/money-float.json
@@@ -0,0 +1,5 @@
++{
++ "charge_nano_usd": "18.5",
++ "released_nano_usd": "82000000",
++ "journal_id": "txn_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/billing.credit-hold-settle-result/invalid/money-number.json b/fixtures/billing.credit-hold-settle-result/invalid/money-number.json
nnew file mode 100644
iindex 00000000..23da3b05
---- /dev/null
++++ b/fixtures/billing.credit-hold-settle-result/invalid/money-number.json
@@@ -0,0 +1,5 @@
++{
++ "charge_nano_usd": 18000,
++ "released_nano_usd": "82000000",
++ "journal_id": "txn_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/billing.credit-hold-settle-result/valid/full.json b/fixtures/billing.credit-hold-settle-result/valid/full.json
nnew file mode 100644
iindex 00000000..bd60fc35
---- /dev/null
++++ b/fixtures/billing.credit-hold-settle-result/valid/full.json
@@@ -0,0 +1,5 @@
++{
++ "charge_nano_usd": "18000",
++ "released_nano_usd": "82000000",
++ "journal_id": "txn_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/billing.credit-hold-settle-result/valid/zero.json b/fixtures/billing.credit-hold-settle-result/valid/zero.json
nnew file mode 100644
iindex 00000000..66bd9087
---- /dev/null
++++ b/fixtures/billing.credit-hold-settle-result/valid/zero.json
@@@ -0,0 +1,5 @@
++{
++ "charge_nano_usd": "0",
++ "released_nano_usd": "50000000",
++ "journal_id": "txn_01JAB2C3D4E5F6G7H8J9K0MNPR"
++}
ddiff --git a/fixtures/billing.credit-hold/invalid/bad-status.json b/fixtures/billing.credit-hold/invalid/bad-status.json
nnew file mode 100644
iindex 00000000..abbeaac0
---- /dev/null
++++ b/fixtures/billing.credit-hold/invalid/bad-status.json
@@@ -0,0 +1,9 @@
++{
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "subject": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "held_nano_usd": "100000000",
++ "available_nano_usd": "75000000",
++ "lease_expires_at": "2026-11-03T01:00:00.000Z",
++ "status": "closed"
++}
ddiff --git a/fixtures/billing.credit-hold/invalid/money-float.json b/fixtures/billing.credit-hold/invalid/money-float.json
nnew file mode 100644
iindex 00000000..7dfe715c
---- /dev/null
++++ b/fixtures/billing.credit-hold/invalid/money-float.json
@@@ -0,0 +1,8 @@
++{
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "subject": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "held_nano_usd": "100000000.5",
++ "available_nano_usd": "75000000",
++ "lease_expires_at": "2026-11-03T01:00:00.000Z"
++}
ddiff --git a/fixtures/billing.credit-hold/invalid/money-number.json b/fixtures/billing.credit-hold/invalid/money-number.json
nnew file mode 100644
iindex 00000000..f3425a54
---- /dev/null
++++ b/fixtures/billing.credit-hold/invalid/money-number.json
@@@ -0,0 +1,8 @@
++{
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "subject": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "held_nano_usd": 100000000,
++ "available_nano_usd": "75000000",
++ "lease_expires_at": "2026-11-03T01:00:00.000Z"
++}
ddiff --git a/fixtures/billing.credit-hold/valid/active.json b/fixtures/billing.credit-hold/valid/active.json
nnew file mode 100644
iindex 00000000..c8f5fe8c
---- /dev/null
++++ b/fixtures/billing.credit-hold/valid/active.json
@@@ -0,0 +1,14 @@
++{
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "subject": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "credential_id": "cred_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "held_nano_usd": "100000000",
++ "available_nano_usd": "75000000",
++ "max_nano_usd": "100000000",
++ "rate_version": "rr-2026-11-03-001",
++ "request_fingerprint": "fp-chat-001",
++ "lease_expires_at": "2026-11-03T01:00:00.000Z",
++ "status": "active",
++ "created_at": "2026-11-03T00:00:00.000Z"
++}
ddiff --git a/fixtures/billing.credit-hold/valid/settled.json b/fixtures/billing.credit-hold/valid/settled.json
nnew file mode 100644
iindex 00000000..5009985c
---- /dev/null
++++ b/fixtures/billing.credit-hold/valid/settled.json
@@@ -0,0 +1,14 @@
++{
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "subject": { "type": "app", "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "credential_id": null,
++ "held_nano_usd": "50000000",
++ "available_nano_usd": "0",
++ "max_nano_usd": "50000000",
++ "rate_version": "rr-2026-11-03-001",
++ "request_fingerprint": "fp-chat-002",
++ "lease_expires_at": "2026-11-03T01:00:00.000Z",
++ "status": "settled",
++ "created_at": "2026-11-03T00:00:00.000Z"
++}
ddiff --git a/fixtures/billing.credit-sandbox-grant-request/invalid/missing-project.json b/fixtures/billing.credit-sandbox-grant-request/invalid/missing-project.json
nnew file mode 100644
iindex 00000000..f046d9b8
---- /dev/null
++++ b/fixtures/billing.credit-sandbox-grant-request/invalid/missing-project.json
@@@ -0,0 +1,7 @@
++{
++ "subject": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "amount_nano_usd": "5000000000"
++}
\\ No newline at end of file
ddiff --git a/fixtures/billing.credit-sandbox-grant-request/invalid/money-float.json b/fixtures/billing.credit-sandbox-grant-request/invalid/money-float.json
nnew file mode 100644
iindex 00000000..c607b920
---- /dev/null
++++ b/fixtures/billing.credit-sandbox-grant-request/invalid/money-float.json
@@@ -0,0 +1,8 @@
++{
++ "subject": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "amount_nano_usd": "5.5"
++}
\\ No newline at end of file
ddiff --git a/fixtures/billing.credit-sandbox-grant-request/invalid/money-number.json b/fixtures/billing.credit-sandbox-grant-request/invalid/money-number.json
nnew file mode 100644
iindex 00000000..6ab7c7ca
---- /dev/null
++++ b/fixtures/billing.credit-sandbox-grant-request/invalid/money-number.json
@@@ -0,0 +1,8 @@
++{
++ "subject": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "amount_nano_usd": 5000000000
++}
\\ No newline at end of file
ddiff --git a/fixtures/billing.credit-sandbox-grant-request/valid/minimal.json b/fixtures/billing.credit-sandbox-grant-request/valid/minimal.json
nnew file mode 100644
iindex 00000000..dbb0ebf8
---- /dev/null
++++ b/fixtures/billing.credit-sandbox-grant-request/valid/minimal.json
@@@ -0,0 +1,8 @@
++{
++ "subject": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "amount_nano_usd": "5000000000"
++}
\\ No newline at end of file
ddiff --git a/fixtures/billing.credit-sandbox-grant-request/valid/with-reason.json b/fixtures/billing.credit-sandbox-grant-request/valid/with-reason.json
nnew file mode 100644
iindex 00000000..a60c6755
---- /dev/null
++++ b/fixtures/billing.credit-sandbox-grant-request/valid/with-reason.json
@@@ -0,0 +1,9 @@
++{
++ "subject": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "amount_nano_usd": "5000000000",
++ "reason": "staging smoke test"
++}
\\ No newline at end of file
ddiff --git a/fixtures/billing.credit-sandbox-grant-result/invalid/bad-id.json b/fixtures/billing.credit-sandbox-grant-result/invalid/bad-id.json
nnew file mode 100644
iindex 00000000..dab8b438
---- /dev/null
++++ b/fixtures/billing.credit-sandbox-grant-result/invalid/bad-id.json
@@@ -0,0 +1,5 @@
++{
++ "grant_id": "grant-1",
++ "amount_nano_usd": "5000000000",
++ "balance_nano_usd": "5000000000"
++}
\\ No newline at end of file
ddiff --git a/fixtures/billing.credit-sandbox-grant-result/invalid/missing-balance.json b/fixtures/billing.credit-sandbox-grant-result/invalid/missing-balance.json
nnew file mode 100644
iindex 00000000..128d802e
---- /dev/null
++++ b/fixtures/billing.credit-sandbox-grant-result/invalid/missing-balance.json
@@@ -0,0 +1,4 @@
++{
++ "grant_id": "igr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1",
++ "amount_nano_usd": "5000000000"
++}
\\ No newline at end of file
ddiff --git a/fixtures/billing.credit-sandbox-grant-result/invalid/money-float.json b/fixtures/billing.credit-sandbox-grant-result/invalid/money-float.json
nnew file mode 100644
iindex 00000000..a8151428
---- /dev/null
++++ b/fixtures/billing.credit-sandbox-grant-result/invalid/money-float.json
@@@ -0,0 +1,5 @@
++{
++ "grant_id": "igr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1",
++ "amount_nano_usd": "5000000000",
++ "balance_nano_usd": "1.5"
++}
\\ No newline at end of file
ddiff --git a/fixtures/billing.credit-sandbox-grant-result/valid/after-spend.json b/fixtures/billing.credit-sandbox-grant-result/valid/after-spend.json
nnew file mode 100644
iindex 00000000..5a8981fb
---- /dev/null
++++ b/fixtures/billing.credit-sandbox-grant-result/valid/after-spend.json
@@@ -0,0 +1,5 @@
++{
++ "grant_id": "igr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1",
++ "amount_nano_usd": "5000000000",
++ "balance_nano_usd": "4999000000"
++}
\\ No newline at end of file
ddiff --git a/fixtures/billing.credit-sandbox-grant-result/valid/full.json b/fixtures/billing.credit-sandbox-grant-result/valid/full.json
nnew file mode 100644
iindex 00000000..1620fee3
---- /dev/null
++++ b/fixtures/billing.credit-sandbox-grant-result/valid/full.json
@@@ -0,0 +1,5 @@
++{
++ "grant_id": "igr_01J8Z3Q4R5S6T7V8W9X0Y1Z2A1",
++ "amount_nano_usd": "5000000000",
++ "balance_nano_usd": "5000000000"
++}
\\ No newline at end of file
ddiff --git a/fixtures/billing.credit-settlement/invalid/missing-usage.json b/fixtures/billing.credit-settlement/invalid/missing-usage.json
nnew file mode 100644
iindex 00000000..c4f90430
---- /dev/null
++++ b/fixtures/billing.credit-settlement/invalid/missing-usage.json
@@@ -0,0 +1,27 @@
++{
++ "settlement_id": "stl_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "subject": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "charge_nano_usd": "18000",
++ "released_nano_usd": "82000000",
++ "journal_id": "txn_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "usage": {
++ "input_tokens": 1000,
++ "cached_read_tokens": 0,
++ "output_tokens": 500
++ },
++ "retail": {
++ "version": "rr-2026-11-03-001",
++ "nano_usd_per_m": {
++ "input": "3000",
++ "cached_read": "300",
++ "cache_write": "3000",
++ "output": "15000"
++ },
++ "per_request_nano_usd": "0"
++ },
++ "supply_cost_nano_usd": "9000",
++ "byok": false,
++ "attempts": []
++}
ddiff --git a/fixtures/billing.credit-settlement/invalid/money-float.json b/fixtures/billing.credit-settlement/invalid/money-float.json
nnew file mode 100644
iindex 00000000..a68c11e1
---- /dev/null
++++ b/fixtures/billing.credit-settlement/invalid/money-float.json
@@@ -0,0 +1,9 @@
++{
++ "settlement_id": "stl_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "subject": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "charge_nano_usd": "18.5",
++ "released_nano_usd": "82000000",
++ "journal_id": "txn_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/billing.credit-settlement/invalid/money-number.json b/fixtures/billing.credit-settlement/invalid/money-number.json
nnew file mode 100644
iindex 00000000..51af9757
---- /dev/null
++++ b/fixtures/billing.credit-settlement/invalid/money-number.json
@@@ -0,0 +1,9 @@
++{
++ "settlement_id": "stl_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "subject": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "charge_nano_usd": 18000,
++ "released_nano_usd": "82000000",
++ "journal_id": "txn_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/billing.credit-settlement/valid/byok-zero.json b/fixtures/billing.credit-settlement/valid/byok-zero.json
nnew file mode 100644
iindex 00000000..97dd60be
---- /dev/null
++++ b/fixtures/billing.credit-settlement/valid/byok-zero.json
@@@ -0,0 +1,37 @@
++{
++ "settlement_id": "stl_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "subject": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "charge_nano_usd": "0",
++ "released_nano_usd": "50000000",
++ "journal_id": "txn_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "usage": {
++ "input_tokens": 500,
++ "cached_read_tokens": 0,
++ "cache_write_tokens": 0,
++ "output_tokens": 200
++ },
++ "retail": {
++ "version": "rr-2026-11-03-001",
++ "nano_usd_per_m": {
++ "input": "0",
++ "cached_read": "0",
++ "cache_write": "0",
++ "output": "0"
++ },
++ "per_request_nano_usd": "0"
++ },
++ "supply_cost_nano_usd": "0",
++ "byok": true,
++ "attempts": [
++ {
++ "deployment_id": "dep-owner-byok",
++ "provider_access": "CUSTOMER_BYOK",
++ "started_at": "2026-11-03T00:00:01.000Z",
++ "ended_at": "2026-11-03T00:00:04.000Z",
++ "error_code": null
++ }
++ ],
++ "created_at": "2026-11-03T00:00:04.000Z"
++}
ddiff --git a/fixtures/billing.credit-settlement/valid/paid.json b/fixtures/billing.credit-settlement/valid/paid.json
nnew file mode 100644
iindex 00000000..713d51ed
---- /dev/null
++++ b/fixtures/billing.credit-settlement/valid/paid.json
@@@ -0,0 +1,37 @@
++{
++ "settlement_id": "stl_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "hold_id": "hld_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "subject": { "type": "user", "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ" },
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "charge_nano_usd": "18000",
++ "released_nano_usd": "82000000",
++ "journal_id": "txn_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "usage": {
++ "input_tokens": 1000,
++ "cached_read_tokens": 0,
++ "cache_write_tokens": 0,
++ "output_tokens": 500
++ },
++ "retail": {
++ "version": "rr-2026-11-03-001",
++ "nano_usd_per_m": {
++ "input": "3000",
++ "cached_read": "300",
++ "cache_write": "3000",
++ "output": "15000"
++ },
++ "per_request_nano_usd": "0"
++ },
++ "supply_cost_nano_usd": "9000",
++ "byok": false,
++ "attempts": [
++ {
++ "deployment_id": "dep-qwen-9b-us-east-001",
++ "provider_access": "OWN_DEPLOYMENT",
++ "started_at": "2026-11-03T00:00:01.000Z",
++ "ended_at": "2026-11-03T00:00:05.000Z",
++ "error_code": null
++ }
++ ],
++ "created_at": "2026-11-03T00:00:05.000Z"
++}
ddiff --git a/fixtures/platform.rate-card/invalid/bad-side.json b/fixtures/platform.rate-card/invalid/bad-side.json
nnew file mode 100644
iindex 00000000..3ad384bd
---- /dev/null
++++ b/fixtures/platform.rate-card/invalid/bad-side.json
@@@ -0,0 +1,13 @@
++{
++ "id": "rc_bad",
++ "provider": "p",
++ "side": "wholesale",
++ "metric": "m",
++ "unit_size": 1,
++ "unit_price_usd": 1,
++ "free_allowance": 0,
++ "reset_period": "month",
++ "effective_from": "2026-09-01",
++ "source": "https://example.com",
++ "verified_at": "2026-09-28"
++}
ddiff --git a/fixtures/platform.rate-card/valid/retail-tokens.json b/fixtures/platform.rate-card/valid/retail-tokens.json
nnew file mode 100644
iindex 00000000..bbf27932
---- /dev/null
++++ b/fixtures/platform.rate-card/valid/retail-tokens.json
@@@ -0,0 +1,16 @@
++{
++ "id": "rc_openai_tokens_retail_v2",
++ "provider": "openai",
++ "adapter": "openai-v1",
++ "side": "retail",
++ "version": "2026-11-03-001",
++ "metric": "tokens",
++ "unit_size": 1000000,
++ "unit_price_usd": 0.15,
++ "free_allowance": 0,
++ "reset_period": "month",
++ "effective_from": "2026-11-01",
++ "effective_to": "2026-12-01",
++ "source": "https://openai.com/api/pricing/",
++ "verified_at": "2026-11-03"
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 65a6cd47..fee08c84 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -16538,14 +16538,14 @@
}
},
"ai.model": {
-- "version": "1.0.0",
++ "version": "1.1.0",
"owner": "ai",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/ai/model.v1.json",
"title": "AiModel",
-- "description": "ai.model@1: one model of a provider in OpenVibe.AI's registry (server/registry.js decodeModel): type, status, limits, per-million-token costs and what it supports.",
++ "description": "ai.model@1: a catalog entry for one model in OpenVibe.AI's registry. The legacy provider/model fields stay for existing readers; new catalog fields (id, family, variant, quant, revision, sha256, license, context, capabilities, modalities, retail rate and availability) are optional so older documents remain valid. Exact ids follow `<org>/<model>[:<quant>]@<revision>`; dynamic aliases such as `openvibe/auto` may be enumerated in `aliases`. Money fields are integer nano-USD per million tokens, as strings.",
"type": "object",
"required": [
"provider_key",
@@@ -16555,6 +16555,13 @@
"cost",
"supports"
],
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
"properties": {
"provider_key": {
"type": "string"
@@@ -16635,6 +16642,128 @@
}
}
},
++ "id": {
++ "type": "string",
++ "pattern": "^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+(:[a-zA-Z0-9._-]+)?@[a-zA-Z0-9._-]+$",
++ "description": "Exact model id: `<org>/<model>[:<quant>]@<revision>`.",
++ "maxLength": 256
++ },
++ "aliases": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "enum": [
++ "openvibe",
++ "openvibe/auto",
++ "openvibe/economy",
++ "openvibe/balanced",
++ "openvibe/fast",
++ "openvibe/private"
++ ]
++ },
++ "description": "Dynamic policy aliases that resolve to this model."
++ },
++ "family": {
++ "type": "string",
++ "description": "Creator and underlying model family, e.g. `qwen3`."
++ },
++ "variant": {
++ "type": "string",
++ "enum": [
++ "original",
++ "abliterated",
++ "finetuned",
++ "merged",
++ "lora"
++ ],
++ "description": "How the weights were produced."
++ },
++ "quant": {
++ "type": "string",
++ "description": "Quantization label, e.g. `q4_k_m`."
++ },
++ "revision": {
++ "type": "string",
++ "description": "Artifact revision or commit hash."
++ },
++ "sha256": {
++ "type": "string",
++ "pattern": "^[a-f0-9]{64}$",
++ "description": "SHA-256 of the weight file or manifest."
++ },
++ "license": {
++ "type": "string",
++ "description": "SPDX license identifier or custom license name."
++ },
++ "context": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "Maximum context length in tokens."
++ },
++ "capabilities": {
++ "type": "object",
++ "properties": {
++ "tools": {
++ "type": "boolean"
++ },
++ "json": {
++ "type": "boolean"
++ },
++ "vision": {
++ "type": "boolean"
++ },
++ "stream": {
++ "type": "boolean"
++ }
++ }
++ },
++ "modalities": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "enum": [
++ "text",
++ "image",
++ "audio",
++ "video"
++ ]
++ },
++ "description": "Input/output modalities the model supports."
++ },
++ "retail_rate": {
++ "type": "object",
++ "description": "Customer retail price, immutable rate version and nano-USD per-million-token component rates.",
++ "properties": {
++ "version": {
++ "type": "string"
++ },
++ "input_nano_usd_per_m": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "cached_read_nano_usd_per_m": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "cache_write_nano_usd_per_m": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "output_nano_usd_per_m": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "per_request_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++ },
++ "availability": {
++ "type": "string",
++ "enum": [
++ "available",
++ "limited",
++ "unavailable",
++ "deprecated"
++ ],
++ "description": "Whether the model can be chosen for new requests."
++ },
"metadata": {
"type": "object"
},
@@@ -16653,6 +16782,438 @@
}
}
},
++ "ai.model-artifact": {
++ "version": "1.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/model-artifact.v1.json",
++ "title": "AiModelArtifact",
++ "description": "ai.model-artifact@1: one pinned revision/variant/quantization of a model family. Identified by an exact id (`<org>/<model>[:<quant>]@<revision>`), with its weight SHA-256, tokenizer revision, license and provenance.",
++ "type": "object",
++ "required": [
++ "id",
++ "family",
++ "variant",
++ "quant",
++ "revision",
++ "sha256",
++ "license"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "pattern": "^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+(:[a-zA-Z0-9._-]+)?@[a-zA-Z0-9._-]+$",
++ "maxLength": 256,
++ "description": "Exact artifact id: `<org>/<model>[:<quant>]@<revision>`."
++ },
++ "family": {
++ "type": "string",
++ "description": "Underlying model family, e.g. `qwen3`."
++ },
++ "variant": {
++ "type": "string",
++ "enum": [
++ "original",
++ "abliterated",
++ "finetuned",
++ "merged",
++ "lora"
++ ],
++ "description": "How the weights were produced."
++ },
++ "quant": {
++ "type": "string",
++ "description": "Quantization label, e.g. `q4_k_m` or `fp16`."
++ },
++ "revision": {
++ "type": "string",
++ "description": "Artifact revision or commit hash."
++ },
++ "sha256": {
++ "type": "string",
++ "pattern": "^[a-f0-9]{64}$",
++ "description": "SHA-256 of the weight file or signed manifest."
++ },
++ "tokenizer_revision": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Tokenizer/chat-template revision used with this artifact."
++ },
++ "license": {
++ "type": "string",
++ "description": "SPDX license identifier or custom license name."
++ },
++ "source_url": {
++ "type": "string",
++ "format": "uri",
++ "description": "Canonical download or provenance URL."
++ },
++ "context": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "Maximum context length in tokens."
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "ai.inference-deployment": {
++ "version": "1.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/inference-deployment.v1.json",
++ "title": "AiInferenceDeployment",
++ "description": "ai.inference-deployment@1: a loaded inference deployment for a pinned artifact. Describes where it runs, its trust tier, commercial rights, health, limits and the rate card that prices its supply cost.",
++ "type": "object",
++ "required": [
++ "id",
++ "artifact_id",
++ "region",
++ "status"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "maxLength": 256,
++ "description": "Deployment id, unique within OpenVibe.AI."
++ },
++ "artifact_id": {
++ "type": "string",
++ "pattern": "^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+(:[a-zA-Z0-9._-]+)?@[a-zA-Z0-9._-]+$",
++ "maxLength": 256,
++ "description": "Exact artifact id this deployment loads."
++ },
++ "region": {
++ "type": "string",
++ "description": "Deployment region or residency label."
++ },
++ "trust_tier": {
++ "type": "string",
++ "enum": [
++ "first_party",
++ "owner_node",
++ "approved_wholesale",
++ "unverified"
++ ],
++ "description": "Who operates the deployment and what data may reach it."
++ },
++ "operator": {
++ "type": "string",
++ "description": "Operator slug or `owner:<subject_id>` for a private node."
++ },
++ "host": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Internal host identifier; never exposed to untrusted callers."
++ },
++ "endpoint": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "uri",
++ "description": "Service endpoint for this deployment (internal or partner)."
++ },
++ "status": {
++ "type": "string",
++ "enum": [
++ "warming",
++ "ready",
++ "full",
++ "degraded",
++ "offline"
++ ],
++ "description": "Current load/health state."
++ },
++ "loaded_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "limits": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "max_concurrency": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "context_tokens": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "max_output_tokens": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ },
++ "supply_rate_card_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Reference to the supply rate card (platform.rate-card@1) that prices this deployment."
++ },
++ "provider_access": {
++ "type": "string",
++ "enum": [
++ "OWN_DEPLOYMENT",
++ "AUTHORIZED_WHOLESALE",
++ "CUSTOMER_BYOK",
++ "RESTRICTED",
++ "UNVERIFIED"
++ ],
++ "description": "Commercial eligibility for paid managed routes."
++ },
++ "commercial_rights": {
++ "type": "string",
++ "enum": [
++ "OWN_DEPLOYMENT",
++ "AUTHORIZED_WHOLESALE",
++ "CUSTOMER_BYOK",
++ "RESTRICTED",
++ "UNVERIFIED"
++ ],
++ "description": "Resale authority recorded for this deployment."
++ },
++ "privacy_policy": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Retention/zero-retention policy label."
++ },
++ "health": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "healthy": {
++ "type": "boolean"
++ },
++ "last_check_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "format": "date-time"
++ },
++ "reason": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ },
++ "updated_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "ai.route-explain": {
++ "version": "1.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/route-explain.v1.json",
++ "title": "AiRouteExplain",
++ "description": "ai.route-explain@1: why OpenVibe.AI chose a model/deployment for a request. Lists the chosen candidate, rejected candidates with reasons, the objective (policy), the pre-flight cost estimate and the final actual cost. Money fields are integer nano-USD, as strings.",
++ "type": "object",
++ "required": [
++ "chosen",
++ "rejected",
++ "objective",
++ "estimate"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "chosen": {
++ "type": "string",
++ "description": "Exact id of the model/deployment that answered."
++ },
++ "rejected": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "id",
++ "reason"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "id": {
++ "type": "string",
++ "description": "Exact id of the rejected candidate."
++ },
++ "reason": {
++ "type": "string",
++ "description": "Human-readable exclusion reason, without secrets or internal host IPs."
++ }
++ }
++ }
++ },
++ "objective": {
++ "type": "string",
++ "enum": [
++ "cheapest",
++ "balanced",
++ "fastest",
++ "best",
++ "private",
++ "exact"
++ ],
++ "description": "Routing policy used for this request."
++ },
++ "estimate": {
++ "type": "object",
++ "required": [
++ "charge_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "charge_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "input_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cached_read_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output_tokens": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "actual": {
++ "type": "object",
++ "additionalProperties": false,
++ "properties": {
++ "charge_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "input_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cached_read_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output_tokens": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "policy": {
++ "type": "string",
++ "description": "Name of the policy/profile that produced the choice."
++ },
++ "fallback_reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "If the first choice could not be used, why the chosen deployment was selected instead."
++ }
++ }
++ }
++ },
++ "ai.model-list": {
++ "version": "1.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/model-list.v1.json",
++ "title": "AiModelList",
++ "description": "ai.model-list@1: paginated list of model catalog entries returned by GET /v1/models.",
++ "type": "object",
++ "required": [
++ "models",
++ "count"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "models": {
++ "type": "array",
++ "items": {
++ "$ref": "model.v1.json"
++ }
++ },
++ "count": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "Number of models in this response."
++ },
++ "cursor": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Opaque cursor for the next page."
++ }
++ }
++ }
++ },
++ "ai.model-read-result": {
++ "version": "1.0.0",
++ "owner": "ai",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/ai/model-read-result.v1.json",
++ "title": "AiModelReadResult",
++ "description": "ai.model-read-result@1: response from GET /v1/models (a list) or GET /v1/models/:id (a single model).",
++ "type": "object",
++ "anyOf": [
++ {
++ "$ref": "model-list.v1.json"
++ },
++ {
++ "$ref": "model.v1.json"
++ }
++ ]
++ }
++ },
"ai.provider-manage-request": {
"version": "1.0.0",
"owner": "ai",
@@@ -26806,6 +27367,643 @@
}
}
},
++ "billing.credit-hold": {
++ "version": "1.0.0",
++ "owner": "billing",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold.v1.json",
++ "title": "BillingCreditHold",
++ "description": "billing.credit-hold@1: a reservation against an account's available inference credit. Holds are created before a request runs, may be extended while streaming, and are settled or released exactly once. Money fields are integer nano-USD, as strings.",
++ "type": "object",
++ "required": [
++ "hold_id",
++ "subject",
++ "project_id",
++ "held_nano_usd",
++ "available_nano_usd",
++ "lease_expires_at"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "hold_id": {
++ "type": "string",
++ "pattern": "^hld_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "ULID hold id."
++ },
++ "subject": {
++ "$ref": "../identity/subject-ref.v1.json"
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "credential_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^cred_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "Developer credential the hold is scoped to, if any."
++ },
++ "held_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "available_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "max_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "rate_version": {
++ "type": "string",
++ "description": "Immutable retail rate-card version the hold was priced against."
++ },
++ "request_fingerprint": {
++ "type": "string",
++ "maxLength": 1024,
++ "description": "Opaque fingerprint used to dedupe the same idempotency key."
++ },
++ "lease_expires_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "status": {
++ "type": "string",
++ "enum": [
++ "active",
++ "extended",
++ "settled",
++ "released",
++ "expired"
++ ]
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{0,39}$",
++ "description": "Which metered service spends the credit (D9): ai, run, ... Defaults to ai."
++ }
++ }
++ }
++ },
++ "billing.credit-settlement": {
++ "version": "1.0.0",
++ "owner": "billing",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-settlement.v1.json",
++ "title": "BillingCreditSettlement",
++ "description": "billing.credit-settlement@1: the final charge for one inference hold. Carries the measured usage, the retail rate version applied, the supply-side cost, whether the route was BYOK, and any attempts/fallbacks. Money fields are integer nano-USD, as strings.",
++ "type": "object",
++ "required": [
++ "settlement_id",
++ "hold_id",
++ "subject",
++ "project_id",
++ "charge_nano_usd",
++ "released_nano_usd",
++ "journal_id"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ },
++ "token-usage": {
++ "type": "object",
++ "required": [
++ "input_tokens",
++ "cached_read_tokens",
++ "cache_write_tokens",
++ "output_tokens"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "input_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cached_read_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output_tokens": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "rate-versioned": {
++ "type": "object",
++ "required": [
++ "version",
++ "nano_usd_per_m",
++ "per_request_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "version": {
++ "type": "string"
++ },
++ "nano_usd_per_m": {
++ "type": "object",
++ "required": [
++ "input",
++ "cached_read",
++ "cache_write",
++ "output"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "input": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "cached_read": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "cache_write": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "output": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++ },
++ "per_request_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++ }
++ },
++ "properties": {
++ "settlement_id": {
++ "type": "string",
++ "pattern": "^stl_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "hold_id": {
++ "type": "string",
++ "pattern": "^hld_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "subject": {
++ "$ref": "../identity/subject-ref.v1.json"
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "charge_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "released_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "journal_id": {
++ "type": "string",
++ "pattern": "^txn_"
++ },
++ "usage": {
++ "$ref": "#/$defs/token-usage"
++ },
++ "retail": {
++ "$ref": "#/$defs/rate-versioned"
++ },
++ "supply_cost_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "byok": {
++ "type": "boolean",
++ "description": "True when the caller supplied their own provider key; retail charge is 0."
++ },
++ "attempts": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "deployment_id",
++ "started_at",
++ "ended_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "deployment_id": {
++ "type": "string"
++ },
++ "provider_access": {
++ "type": "string",
++ "enum": [
++ "OWN_DEPLOYMENT",
++ "AUTHORIZED_WHOLESALE",
++ "CUSTOMER_BYOK",
++ "RESTRICTED",
++ "UNVERIFIED"
++ ]
++ },
++ "started_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "ended_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "error_code": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ },
++ "created_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "shortfall_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Usage beyond the hold and the free balance: owed, not collected (nano-USD, D1)."
++ }
++ }
++ }
++ },
++ "billing.credit-hold-create-request": {
++ "version": "1.0.0",
++ "owner": "billing",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold-create-request.v1.json",
++ "title": "BillingCreditHoldCreateRequest",
++ "description": "billing.credit-hold-create-request@1: body of POST /internal/credits/holds. Reserves inference credit up to max_nano_usd against a (subject, project_id) account, priced at the given immutable rate version.",
++ "type": "object",
++ "required": [
++ "idempotency_key",
++ "subject",
++ "project_id",
++ "max_nano_usd",
++ "rate_version",
++ "request_fingerprint"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "idempotency_key": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9._:-]{8,128}$"
++ },
++ "subject": {
++ "$ref": "../identity/subject-ref.v1.json"
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "credential_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^cred_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "max_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "rate_version": {
++ "type": "string"
++ },
++ "request_fingerprint": {
++ "type": "string",
++ "maxLength": 1024
++ },
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{0,39}$",
++ "description": "Which metered service spends the credit (D9): ai, run, ... Defaults to ai."
++ }
++ }
++ }
++ },
++ "billing.credit-hold-create-result": {
++ "version": "1.0.0",
++ "owner": "billing",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold-create-result.v1.json",
++ "title": "BillingCreditHoldCreateResult",
++ "description": "billing.credit-hold-create-result@1: response from POST /internal/credits/holds. Returns the created or replayed hold.",
++ "type": "object",
++ "required": [
++ "hold_id",
++ "held_nano_usd",
++ "available_nano_usd",
++ "lease_expires_at"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "hold_id": {
++ "type": "string",
++ "pattern": "^hld_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "held_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "available_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "lease_expires_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ }
++ },
++ "billing.credit-hold-extend-request": {
++ "version": "1.0.0",
++ "owner": "billing",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold-extend-request.v1.json",
++ "title": "BillingCreditHoldExtendRequest",
++ "description": "billing.credit-hold-extend-request@1: body of POST /internal/credits/holds/:id/extend. Grows an active hold by add_nano_usd.",
++ "type": "object",
++ "required": [
++ "add_nano_usd"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "add_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++ }
++ },
++ "billing.credit-hold-settle-request": {
++ "version": "1.0.0",
++ "owner": "billing",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold-settle-request.v1.json",
++ "title": "BillingCreditHoldSettleRequest",
++ "description": "billing.credit-hold-settle-request@1: body of POST /internal/credits/holds/:id/settle. Idempotent: the same key and fingerprint return the same settlement.",
++ "type": "object",
++ "required": [
++ "idempotency_key",
++ "usage",
++ "retail",
++ "supply_cost_nano_usd",
++ "byok",
++ "attempts"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "idempotency_key": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9._:-]{8,128}$"
++ },
++ "usage": {
++ "type": "object",
++ "required": [
++ "input_tokens",
++ "cached_read_tokens",
++ "cache_write_tokens",
++ "output_tokens"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "input_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cached_read_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output_tokens": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "retail": {
++ "type": "object",
++ "required": [
++ "version",
++ "nano_usd_per_m",
++ "per_request_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "version": {
++ "type": "string"
++ },
++ "nano_usd_per_m": {
++ "type": "object",
++ "required": [
++ "input",
++ "cached_read",
++ "cache_write",
++ "output"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "input": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "cached_read": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "cache_write": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "output": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++ },
++ "per_request_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++ },
++ "supply_cost_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "byok": {
++ "type": "boolean"
++ },
++ "attempts": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "deployment_id",
++ "started_at",
++ "ended_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "deployment_id": {
++ "type": "string"
++ },
++ "provider_access": {
++ "type": "string",
++ "enum": [
++ "OWN_DEPLOYMENT",
++ "AUTHORIZED_WHOLESALE",
++ "CUSTOMER_BYOK",
++ "RESTRICTED",
++ "UNVERIFIED"
++ ]
++ },
++ "started_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "ended_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "error_code": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "billing.credit-hold-settle-result": {
++ "version": "1.0.0",
++ "owner": "billing",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold-settle-result.v1.json",
++ "title": "BillingCreditHoldSettleResult",
++ "description": "billing.credit-hold-settle-result@1: response from POST /internal/credits/holds/:id/settle.",
++ "type": "object",
++ "required": [
++ "charge_nano_usd",
++ "released_nano_usd",
++ "journal_id"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "charge_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "released_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ },
++ "journal_id": {
++ "type": "string",
++ "pattern": "^txn_"
++ },
++ "shortfall_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Usage beyond the hold and the free balance: owed, not collected (nano-USD, D1)."
++ }
++ }
++ }
++ },
++ "billing.credit-hold-release-result": {
++ "version": "1.0.0",
++ "owner": "billing",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-hold-release-result.v1.json",
++ "title": "BillingCreditHoldReleaseResult",
++ "description": "billing.credit-hold-release-result@1: response from POST /internal/credits/holds/:id/release.",
++ "type": "object",
++ "required": [
++ "hold_id",
++ "released_nano_usd"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "hold_id": {
++ "type": "string",
++ "pattern": "^hld_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "released_nano_usd": {
++ "$ref": "#/$defs/money-nano-usd"
++ }
++ }
++ }
++ },
"deals.watch.matched": {
"version": "1.0.0",
"owner": "deals",
@@@ -69055,14 +70253,14 @@
}
},
"platform.rate-card": {
-- "version": "1.0.0",
++ "version": "1.1.0",
"owner": "network",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/platform/rate-card.v1.json",
"title": "RateCard",
-- "description": "One provider price with its free allowance, in the provider's real billing unit, with effective dates and where it was checked (roadmap WS-Z3 task 5). Changed only by review; never written by an AI on its own.",
++ "description": "One provider price with its free allowance, in the provider's real billing unit, with effective dates and where it was checked (roadmap WS-Z3 task 5). v1.1 adds optional `side` (supply|retail), `version` and `effective_to`; existing documents stay valid. Changed only by review; never written by an AI on its own.",
"type": "object",
"additionalProperties": false,
"required": [
@@@ -69087,6 +70285,18 @@
"adapter": {
"type": "string"
},
++ "side": {
++ "type": "string",
++ "enum": [
++ "supply",
++ "retail"
++ ],
++ "description": "Whether this card prices what OpenVibe pays (supply) or what the customer pays (retail)."
++ },
++ "version": {
++ "type": "string",
++ "description": "Immutable version of this card; settlements reference it so historical prices survive updates."
++ },
"metric": {
"type": "string",
"description": "e.g. queue-operation-64kb, request, gib-delivered, message, gb-month, class-a-op"
@@@ -69118,6 +70328,11 @@
"type": "string",
"format": "date"
},
++ "effective_to": {
++ "type": "string",
++ "format": "date",
++ "description": "Inclusive end date; null or absent means open-ended."
++ },
"effective_until": {
"type": "string",
"format": "date"
@@@ -98928,6 +100143,76 @@
}
}
}
++ },
++ "billing.credit-sandbox-grant-request": {
++ "version": "1.0.0",
++ "owner": "billing",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-sandbox-grant-request.v1.json",
++ "title": "BillingCreditSandboxGrantRequest",
++ "description": "billing.credit-sandbox-grant-request@1: POST /internal/credits/sandbox/grants, synthetic credit for tests and staging. Billing answers only when BILLING_CREDITS_SANDBOX=1; the grant is a test transaction (D9).",
++ "type": "object",
++ "required": [
++ "subject",
++ "project_id",
++ "amount_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "$ref": "../identity/subject-ref.v1.json"
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "amount_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ },
++ "reason": {
++ "type": "string",
++ "maxLength": 500
++ }
++ }
++ }
++ },
++ "billing.credit-sandbox-grant-result": {
++ "version": "1.0.0",
++ "owner": "billing",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/billing/credit-sandbox-grant-result.v1.json",
++ "title": "BillingCreditSandboxGrantResult",
++ "description": "billing.credit-sandbox-grant-result@1: the grant and the balance after it.",
++ "type": "object",
++ "required": [
++ "grant_id",
++ "amount_nano_usd",
++ "balance_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "grant_id": {
++ "type": "string",
++ "pattern": "^igr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "amount_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ },
++ "balance_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ }
++ }
}
}
}
ddiff --git a/generated/openapi/actor.json b/generated/openapi/actor.json
iindex 0f580b96..0960ccf8 100644
---- a/generated/openapi/actor.json
++++ b/generated/openapi/actor.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Actor API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/ai.json b/generated/openapi/ai.json
iindex 4ae1c425..60306bb2 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.283.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.284.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"
@@@ -3949,6 +3949,220 @@
}
}
}
++ },
++ "/v1/chat/completions": {
++ "post": {
++ "operationId": "post_v1_chat_completions",
++ "summary": "Create developer-app inference requests (D8): POST /v1/chat/completions and POST /v1/messages.",
++ "description": "**ai.inference.create** (public): Create developer-app inference requests (D8): POST /v1/chat/completions and POST /v1/messages. Grantable to developer apps; the token's project_id scopes quota and spend. ai.run.create stays first-party for workflows and runs.",
++ "tags": [
++ "ai.inference"
++ ],
++ "x-openvibe-capabilities": [
++ "ai.inference.create"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "ai-inference",
++ "security": [
++ {
++ "openvibeToken": [
++ "ai.inference.create"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.run-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.run.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/v1/messages": {
++ "post": {
++ "operationId": "post_v1_messages",
++ "summary": "Create developer-app inference requests (D8): POST /v1/chat/completions and POST /v1/messages.",
++ "description": "**ai.inference.create** (public): Create developer-app inference requests (D8): POST /v1/chat/completions and POST /v1/messages. Grantable to developer apps; the token's project_id scopes quota and spend. ai.run.create stays first-party for workflows and runs.",
++ "tags": [
++ "ai.inference"
++ ],
++ "x-openvibe-capabilities": [
++ "ai.inference.create"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "ai-inference",
++ "security": [
++ {
++ "openvibeToken": [
++ "ai.inference.create"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.run-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.run.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/v1/models": {
++ "get": {
++ "operationId": "get_v1_models",
++ "summary": "Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1).",
++ "description": "**ai.model.read** (public): Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1). Available to developer apps and anonymous callers.",
++ "tags": [
++ "ai.model"
++ ],
++ "x-openvibe-capabilities": [
++ "ai.model.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "ai-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "ai.model.read"
++ ]
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.model-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/v1/models/{id}": {
++ "get": {
++ "operationId": "get_v1_models_id",
++ "summary": "Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1).",
++ "description": "**ai.model.read** (public): Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1). Available to developer apps and anonymous callers.",
++ "tags": [
++ "ai.model"
++ ],
++ "x-openvibe-capabilities": [
++ "ai.model.read"
++ ],
++ "x-openvibe-visibility": [
++ "public"
++ ],
++ "x-openvibe-quota-class": "ai-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "ai.model.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/ai.model-read-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
}
},
"components": {
@@@ -5106,9 +5320,52 @@
}
}
},
++ "ai.model-list.v1": {
++ "title": "AiModelList",
++ "description": "ai.model-list@1: paginated list of model catalog entries returned by GET /v1/models.",
++ "type": "object",
++ "required": [
++ "models",
++ "count"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "models": {
++ "type": "array",
++ "items": {
++ "$ref": "#/components/schemas/ai.model.v1"
++ }
++ },
++ "count": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "Number of models in this response."
++ },
++ "cursor": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Opaque cursor for the next page."
++ }
++ }
++ },
++ "ai.model-read-result.v1": {
++ "title": "AiModelReadResult",
++ "description": "ai.model-read-result@1: response from GET /v1/models (a list) or GET /v1/models/:id (a single model).",
++ "type": "object",
++ "anyOf": [
++ {
++ "$ref": "#/components/schemas/ai.model-list.v1"
++ },
++ {
++ "$ref": "#/components/schemas/ai.model.v1"
++ }
++ ]
++ },
"ai.model.v1": {
"title": "AiModel",
-- "description": "ai.model@1: one model of a provider in OpenVibe.AI's registry (server/registry.js decodeModel): type, status, limits, per-million-token costs and what it supports.",
++ "description": "ai.model@1: a catalog entry for one model in OpenVibe.AI's registry. The legacy provider/model fields stay for existing readers; new catalog fields (id, family, variant, quant, revision, sha256, license, context, capabilities, modalities, retail rate and availability) are optional so older documents remain valid. Exact ids follow `<org>/<model>[:<quant>]@<revision>`; dynamic aliases such as `openvibe/auto` may be enumerated in `aliases`. Money fields are integer nano-USD per million tokens, as strings.",
"type": "object",
"required": [
"provider_key",
@@@ -5118,6 +5375,13 @@
"cost",
"supports"
],
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
"properties": {
"provider_key": {
"type": "string"
@@@ -5198,6 +5462,128 @@
}
}
},
++ "id": {
++ "type": "string",
++ "pattern": "^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+(:[a-zA-Z0-9._-]+)?@[a-zA-Z0-9._-]+$",
++ "description": "Exact model id: `<org>/<model>[:<quant>]@<revision>`.",
++ "maxLength": 256
++ },
++ "aliases": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "enum": [
++ "openvibe",
++ "openvibe/auto",
++ "openvibe/economy",
++ "openvibe/balanced",
++ "openvibe/fast",
++ "openvibe/private"
++ ]
++ },
++ "description": "Dynamic policy aliases that resolve to this model."
++ },
++ "family": {
++ "type": "string",
++ "description": "Creator and underlying model family, e.g. `qwen3`."
++ },
++ "variant": {
++ "type": "string",
++ "enum": [
++ "original",
++ "abliterated",
++ "finetuned",
++ "merged",
++ "lora"
++ ],
++ "description": "How the weights were produced."
++ },
++ "quant": {
++ "type": "string",
++ "description": "Quantization label, e.g. `q4_k_m`."
++ },
++ "revision": {
++ "type": "string",
++ "description": "Artifact revision or commit hash."
++ },
++ "sha256": {
++ "type": "string",
++ "pattern": "^[a-f0-9]{64}$",
++ "description": "SHA-256 of the weight file or manifest."
++ },
++ "license": {
++ "type": "string",
++ "description": "SPDX license identifier or custom license name."
++ },
++ "context": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "Maximum context length in tokens."
++ },
++ "capabilities": {
++ "type": "object",
++ "properties": {
++ "tools": {
++ "type": "boolean"
++ },
++ "json": {
++ "type": "boolean"
++ },
++ "vision": {
++ "type": "boolean"
++ },
++ "stream": {
++ "type": "boolean"
++ }
++ }
++ },
++ "modalities": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "enum": [
++ "text",
++ "image",
++ "audio",
++ "video"
++ ]
++ },
++ "description": "Input/output modalities the model supports."
++ },
++ "retail_rate": {
++ "type": "object",
++ "description": "Customer retail price, immutable rate version and nano-USD per-million-token component rates.",
++ "properties": {
++ "version": {
++ "type": "string"
++ },
++ "input_nano_usd_per_m": {
++ "$ref": "#/components/schemas/ai.model.v1/$defs/money-nano-usd"
++ },
++ "cached_read_nano_usd_per_m": {
++ "$ref": "#/components/schemas/ai.model.v1/$defs/money-nano-usd"
++ },
++ "cache_write_nano_usd_per_m": {
++ "$ref": "#/components/schemas/ai.model.v1/$defs/money-nano-usd"
++ },
++ "output_nano_usd_per_m": {
++ "$ref": "#/components/schemas/ai.model.v1/$defs/money-nano-usd"
++ },
++ "per_request_nano_usd": {
++ "$ref": "#/components/schemas/ai.model.v1/$defs/money-nano-usd"
++ }
++ }
++ },
++ "availability": {
++ "type": "string",
++ "enum": [
++ "available",
++ "limited",
++ "unavailable",
++ "deprecated"
++ ],
++ "description": "Whether the model can be chosen for new requests."
++ },
"metadata": {
"type": "object"
},
ddiff --git a/generated/openapi/bench.json b/generated/openapi/bench.json
iindex fbdbd957..12873fb2 100644
---- a/generated/openapi/bench.json
++++ b/generated/openapi/bench.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bench API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Bench as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 bbc67fd7..4f130e90 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.283.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.284.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"
@@@ -1932,6 +1932,306 @@
}
}
}
++ },
++ "/internal/credits/holds": {
++ "post": {
++ "operationId": "post_internal_credits_holds",
++ "summary": "Create an inference credit hold (D2): POST /internal/credits/holds.",
++ "description": "**billing.credit.hold.create** (internal): Create an inference credit hold (D2): POST /internal/credits/holds. Service-authenticated; reserves up to max_nano_usd against a (subject, project_id) account.",
++ "tags": [
++ "billing.credit"
++ ],
++ "x-openvibe-capabilities": [
++ "billing.credit.hold.create"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "billing-credit",
++ "security": [
++ {
++ "openvibeToken": [
++ "billing.credit.hold.create"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/billing.credit-hold-create-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/billing.credit-hold-create-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/credits/holds/{id}/extend": {
++ "post": {
++ "operationId": "post_internal_credits_holds_id_extend",
++ "summary": "Extend an active inference hold (D2): POST /internal/credits/holds/:id/extend.",
++ "description": "**billing.credit.hold.extend** (internal): Extend an active inference hold (D2): POST /internal/credits/holds/:id/extend. Grows the held amount mid-stream; returns 402 billing.insufficient_funds if the balance is short.",
++ "tags": [
++ "billing.credit"
++ ],
++ "x-openvibe-capabilities": [
++ "billing.credit.hold.extend"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "billing-credit",
++ "security": [
++ {
++ "openvibeToken": [
++ "billing.credit.hold.extend"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/billing.credit-hold-extend-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/billing.credit-hold-create-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/credits/holds/{id}/release": {
++ "post": {
++ "operationId": "post_internal_credits_holds_id_release",
++ "summary": "Release a credit hold (D2): POST /internal/credits/holds/:id/release.",
++ "description": "**billing.credit.hold.release** (internal): Release a credit hold (D2): POST /internal/credits/holds/:id/release. Releases the remaining held amount with nothing charged.",
++ "tags": [
++ "billing.credit"
++ ],
++ "x-openvibe-capabilities": [
++ "billing.credit.hold.release"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "billing-credit",
++ "security": [
++ {
++ "openvibeToken": [
++ "billing.credit.hold.release"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/billing.credit-hold-release-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/credits/holds/{id}/settle": {
++ "post": {
++ "operationId": "post_internal_credits_holds_id_settle",
++ "summary": "Settle a credit hold (D2): POST /internal/credits/holds/:id/settle.",
++ "description": "**billing.credit.hold.settle** (internal): Settle a credit hold (D2): POST /internal/credits/holds/:id/settle. Records measured usage and the immutable retail rate version, charges the project account and releases the unused hold. Idempotent: replays return the same settlement.",
++ "tags": [
++ "billing.credit"
++ ],
++ "x-openvibe-capabilities": [
++ "billing.credit.hold.settle"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "billing-credit",
++ "security": [
++ {
++ "openvibeToken": [
++ "billing.credit.hold.settle"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/billing.credit-hold-settle-request.v1"
++ }
++ }
++ }
++ },
++ "parameters": [
++ {
++ "name": "id",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/billing.credit-hold-settle-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/credits/sandbox/grants": {
++ "post": {
++ "operationId": "post_internal_credits_sandbox_grants",
++ "summary": "Grant synthetic credit to a (subject, project) for tests and staging (D9): POST /internal/credits/sandbox/grants.",
++ "description": "**billing.credit.sandbox.grant** (internal): Grant synthetic credit to a (subject, project) for tests and staging (D9): POST /internal/credits/sandbox/grants. Refused unless BILLING_CREDITS_SANDBOX=1; a test transaction, never money.",
++ "tags": [
++ "billing.credit"
++ ],
++ "x-openvibe-capabilities": [
++ "billing.credit.sandbox.grant"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "billing-credit",
++ "security": [
++ {
++ "openvibeToken": [
++ "billing.credit.sandbox.grant"
++ ]
++ }
++ ],
++ "requestBody": {
++ "required": false,
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/billing.credit-sandbox-grant-request.v1"
++ }
++ }
++ }
++ },
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/billing.credit-sandbox-grant-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
}
},
"components": {
@@@ -2751,6 +3051,378 @@
}
}
},
++ "billing.credit-hold-create-request.v1": {
++ "title": "BillingCreditHoldCreateRequest",
++ "description": "billing.credit-hold-create-request@1: body of POST /internal/credits/holds. Reserves inference credit up to max_nano_usd against a (subject, project_id) account, priced at the given immutable rate version.",
++ "type": "object",
++ "required": [
++ "idempotency_key",
++ "subject",
++ "project_id",
++ "max_nano_usd",
++ "rate_version",
++ "request_fingerprint"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "idempotency_key": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9._:-]{8,128}$"
++ },
++ "subject": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "credential_id": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "pattern": "^cred_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "max_nano_usd": {
++ "$ref": "#/components/schemas/billing.credit-hold-create-request.v1/$defs/money-nano-usd"
++ },
++ "rate_version": {
++ "type": "string"
++ },
++ "request_fingerprint": {
++ "type": "string",
++ "maxLength": 1024
++ },
++ "service": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9-]{0,39}$",
++ "description": "Which metered service spends the credit (D9): ai, run, ... Defaults to ai."
++ }
++ }
++ },
++ "billing.credit-hold-create-result.v1": {
++ "title": "BillingCreditHoldCreateResult",
++ "description": "billing.credit-hold-create-result@1: response from POST /internal/credits/holds. Returns the created or replayed hold.",
++ "type": "object",
++ "required": [
++ "hold_id",
++ "held_nano_usd",
++ "available_nano_usd",
++ "lease_expires_at"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "hold_id": {
++ "type": "string",
++ "pattern": "^hld_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "held_nano_usd": {
++ "$ref": "#/components/schemas/billing.credit-hold-create-result.v1/$defs/money-nano-usd"
++ },
++ "available_nano_usd": {
++ "$ref": "#/components/schemas/billing.credit-hold-create-result.v1/$defs/money-nano-usd"
++ },
++ "lease_expires_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ }
++ },
++ "billing.credit-hold-extend-request.v1": {
++ "title": "BillingCreditHoldExtendRequest",
++ "description": "billing.credit-hold-extend-request@1: body of POST /internal/credits/holds/:id/extend. Grows an active hold by add_nano_usd.",
++ "type": "object",
++ "required": [
++ "add_nano_usd"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "add_nano_usd": {
++ "$ref": "#/components/schemas/billing.credit-hold-extend-request.v1/$defs/money-nano-usd"
++ }
++ }
++ },
++ "billing.credit-hold-release-result.v1": {
++ "title": "BillingCreditHoldReleaseResult",
++ "description": "billing.credit-hold-release-result@1: response from POST /internal/credits/holds/:id/release.",
++ "type": "object",
++ "required": [
++ "hold_id",
++ "released_nano_usd"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "hold_id": {
++ "type": "string",
++ "pattern": "^hld_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "released_nano_usd": {
++ "$ref": "#/components/schemas/billing.credit-hold-release-result.v1/$defs/money-nano-usd"
++ }
++ }
++ },
++ "billing.credit-hold-settle-request.v1": {
++ "title": "BillingCreditHoldSettleRequest",
++ "description": "billing.credit-hold-settle-request@1: body of POST /internal/credits/holds/:id/settle. Idempotent: the same key and fingerprint return the same settlement.",
++ "type": "object",
++ "required": [
++ "idempotency_key",
++ "usage",
++ "retail",
++ "supply_cost_nano_usd",
++ "byok",
++ "attempts"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "idempotency_key": {
++ "type": "string",
++ "pattern": "^[A-Za-z0-9._:-]{8,128}$"
++ },
++ "usage": {
++ "type": "object",
++ "required": [
++ "input_tokens",
++ "cached_read_tokens",
++ "cache_write_tokens",
++ "output_tokens"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "input_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cached_read_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "cache_write_tokens": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "output_tokens": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ "retail": {
++ "type": "object",
++ "required": [
++ "version",
++ "nano_usd_per_m",
++ "per_request_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "version": {
++ "type": "string"
++ },
++ "nano_usd_per_m": {
++ "type": "object",
++ "required": [
++ "input",
++ "cached_read",
++ "cache_write",
++ "output"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "input": {
++ "$ref": "#/components/schemas/billing.credit-hold-settle-request.v1/$defs/money-nano-usd"
++ },
++ "cached_read": {
++ "$ref": "#/components/schemas/billing.credit-hold-settle-request.v1/$defs/money-nano-usd"
++ },
++ "cache_write": {
++ "$ref": "#/components/schemas/billing.credit-hold-settle-request.v1/$defs/money-nano-usd"
++ },
++ "output": {
++ "$ref": "#/components/schemas/billing.credit-hold-settle-request.v1/$defs/money-nano-usd"
++ }
++ }
++ },
++ "per_request_nano_usd": {
++ "$ref": "#/components/schemas/billing.credit-hold-settle-request.v1/$defs/money-nano-usd"
++ }
++ }
++ },
++ "supply_cost_nano_usd": {
++ "$ref": "#/components/schemas/billing.credit-hold-settle-request.v1/$defs/money-nano-usd"
++ },
++ "byok": {
++ "type": "boolean"
++ },
++ "attempts": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "deployment_id",
++ "started_at",
++ "ended_at"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "deployment_id": {
++ "type": "string"
++ },
++ "provider_access": {
++ "type": "string",
++ "enum": [
++ "OWN_DEPLOYMENT",
++ "AUTHORIZED_WHOLESALE",
++ "CUSTOMER_BYOK",
++ "RESTRICTED",
++ "UNVERIFIED"
++ ]
++ },
++ "started_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "ended_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "error_code": {
++ "type": [
++ "string",
++ "null"
++ ]
++ }
++ }
++ }
++ }
++ }
++ },
++ "billing.credit-hold-settle-result.v1": {
++ "title": "BillingCreditHoldSettleResult",
++ "description": "billing.credit-hold-settle-result@1: response from POST /internal/credits/holds/:id/settle.",
++ "type": "object",
++ "required": [
++ "charge_nano_usd",
++ "released_nano_usd",
++ "journal_id"
++ ],
++ "additionalProperties": false,
++ "$defs": {
++ "money-nano-usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ },
++ "properties": {
++ "charge_nano_usd": {
++ "$ref": "#/components/schemas/billing.credit-hold-settle-result.v1/$defs/money-nano-usd"
++ },
++ "released_nano_usd": {
++ "$ref": "#/components/schemas/billing.credit-hold-settle-result.v1/$defs/money-nano-usd"
++ },
++ "journal_id": {
++ "type": "string",
++ "pattern": "^txn_"
++ },
++ "shortfall_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Usage beyond the hold and the free balance: owed, not collected (nano-USD, D1)."
++ }
++ }
++ },
++ "billing.credit-sandbox-grant-request.v1": {
++ "title": "BillingCreditSandboxGrantRequest",
++ "description": "billing.credit-sandbox-grant-request@1: POST /internal/credits/sandbox/grants, synthetic credit for tests and staging. Billing answers only when BILLING_CREDITS_SANDBOX=1; the grant is a test transaction (D9).",
++ "type": "object",
++ "required": [
++ "subject",
++ "project_id",
++ "amount_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "subject": {
++ "$ref": "#/components/schemas/identity.subject-ref.v1"
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "amount_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ },
++ "reason": {
++ "type": "string",
++ "maxLength": 500
++ }
++ }
++ },
++ "billing.credit-sandbox-grant-result.v1": {
++ "title": "BillingCreditSandboxGrantResult",
++ "description": "billing.credit-sandbox-grant-result@1: the grant and the balance after it.",
++ "type": "object",
++ "required": [
++ "grant_id",
++ "amount_nano_usd",
++ "balance_nano_usd"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "grant_id": {
++ "type": "string",
++ "pattern": "^igr_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "amount_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ },
++ "balance_nano_usd": {
++ "type": "string",
++ "pattern": "^[0-9]{1,19}$",
++ "description": "Integer nano-USD (1e-9 USD), stored as a string."
++ }
++ }
++ },
"billing.entitlement-result.v1": {
"title": "BillingEntitlementResult",
"description": "billing.entitlement-result@1: answers of billing.entitlement.check on OpenVibe.Billing. GET /api/v1/entitlements/:subject?streamer= → billing.entitlement@1; without streamer → { subject, entitlements } (every active one); GET /api/v1/subscriptions → { subscriptions }; GET /api/v1/subscriptions/:id → { subscription, entitlement }.",
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex 7c2dbdf6..3a23c2e6 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.283.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/bot.json b/generated/openapi/bot.json
iindex 6a928e71..58086cc2 100644
---- a/generated/openapi/bot.json
++++ b/generated/openapi/bot.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Bot API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 37a5111d..478c2562 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.283.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 8de303c6..b6d82f96 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.283.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 a37e40f2..2720a846 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.283.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 d8939d5b..8f65565d 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.283.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 42afffb7..149d5753 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.283.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/domains.json b/generated/openapi/domains.json
iindex c9483ece..6735bca6 100644
---- a/generated/openapi/domains.json
++++ b/generated/openapi/domains.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Domains API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Domains as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 3c043f9e..24b13bb4 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.283.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/food.json b/generated/openapi/food.json
iindex 01861c4c..6f6178d3 100644
---- a/generated/openapi/food.json
++++ b/generated/openapi/food.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Food API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 7c9cf39e..02ba9c6e 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.283.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 3edfd35b..d85667c9 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.283.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/index.json b/generated/openapi/index.json
iindex 9c9de5ed..a9e5bae1 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -12,8 +12,8 @@
"service": "ai",
"name": "OpenVibe.AI",
"origin": "https://ai.openvibe.services",
-- "operations": 58,
-- "capabilities": 13
++ "operations": 62,
++ "capabilities": 15
},
{
"service": "bench",
@@@ -26,8 +26,8 @@
"service": "billing",
"name": "OpenVibe.Billing",
"origin": "https://billing.openvibe.network",
-- "operations": 33,
-- "capabilities": 11
++ "operations": 38,
++ "capabilities": 16
},
{
"service": "blog",
ddiff --git a/generated/openapi/inventory.json b/generated/openapi/inventory.json
iindex 52a6deff..16810a0c 100644
---- a/generated/openapi/inventory.json
++++ b/generated/openapi/inventory.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Inventory API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 e4004f40..f0e33ce7 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.283.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.284.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-hub.json b/generated/openapi/media-hub.json
iindex 54ba5c94..8a4b2077 100644
---- a/generated/openapi/media-hub.json
++++ b/generated/openapi/media-hub.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.MediaHub API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 d8458e94..8aa261b2 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.283.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 f08a4a78..15dfbce7 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.283.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 4f7e7d34..1d0260ad 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.283.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 06a0d74e..bff2b7c9 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": "OpenRestream API",
-- "version": "0.283.0",
-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/play.json b/generated/openapi/play.json
iindex a9a4c95a..129b445d 100644
---- a/generated/openapi/play.json
++++ b/generated/openapi/play.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Play API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/quest.json b/generated/openapi/quest.json
iindex 90f626e7..c060524b 100644
---- a/generated/openapi/quest.json
++++ b/generated/openapi/quest.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Quest API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/report.json b/generated/openapi/report.json
iindex b321292e..26ae984c 100644
---- a/generated/openapi/report.json
++++ b/generated/openapi/report.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Report API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Report as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 b7f5f522..c203c005 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.283.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/run.json b/generated/openapi/run.json
iindex 418094ac..b3d836d2 100644
---- a/generated/openapi/run.json
++++ b/generated/openapi/run.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Run API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 46d0fe4c..ee158ba5 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.283.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/services.json b/generated/openapi/services.json
iindex 7bc4ae1a..eccf4d00 100644
---- a/generated/openapi/services.json
++++ b/generated/openapi/services.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Services API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 24cd4f2f..8827cf27 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.283.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/space.json b/generated/openapi/space.json
iindex 4502f4b9..1334fa84 100644
---- a/generated/openapi/space.json
++++ b/generated/openapi/space.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Space API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 de81e46c..c83585da 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.283.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/today.json b/generated/openapi/today.json
iindex 4ab3e045..a0ee849a 100644
---- a/generated/openapi/today.json
++++ b/generated/openapi/today.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Today API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Today as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 d9ee36d9..78f6ca2b 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.283.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 37bf155d..9924750c 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.283.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 bd12e89c..1d2b2b5c 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.283.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/vision.json b/generated/openapi/vision.json
iindex fc0b7dd6..3b153304 100644
---- a/generated/openapi/vision.json
++++ b/generated/openapi/vision.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Vision API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Vision as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/watch.json b/generated/openapi/watch.json
iindex aa1ae638..873fd189 100644
---- a/generated/openapi/watch.json
++++ b/generated/openapi/watch.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Watch API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 15ecf2e7..c9c5d3d5 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.283.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/work.json b/generated/openapi/work.json
iindex f7c54d36..b0d4755b 100644
---- a/generated/openapi/work.json
++++ b/generated/openapi/work.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Work API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.284.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/zone.json b/generated/openapi/zone.json
iindex c4b7aa3a..ec1be6fa 100644
---- a/generated/openapi/zone.json
++++ b/generated/openapi/zone.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Zone API",
-- "version": "0.283.0",
-- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.283.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.284.0",
++ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.284.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 97434f58..41e1cb6d 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -153008,9 +153008,9 @@ export type AiDefinitionVersionResult =
};
};
--/** [email protected] (owner: ai) */
++/** [email protected] (owner: ai) */
/**
-- * ai.model@1: one model of a provider in OpenVibe.AI's registry (server/registry.js decodeModel): type, status, limits, per-million-token costs and what it supports.
++ * ai.model@1: a catalog entry for one model in OpenVibe.AI's registry. The legacy provider/model fields stay for existing readers; new catalog fields (id, family, variant, quant, revision, sha256, license, context, capabilities, modalities, retail rate and availability) are optional so older documents remain valid. Exact ids follow `<org>/<model>[:<quant>]@<revision>`; dynamic aliases such as `openvibe/auto` may be enumerated in `aliases`. Money fields are integer nano-USD per million tokens, as strings.
*/
export interface AiModel {
provider_key: string;
@@@ -153031,11 +153031,279 @@ export interface AiModel {
streaming?: boolean;
vision?: boolean;
};
++ /**
++ * Exact model id: `<org>/<model>[:<quant>]@<revision>`.
++ */
++ id?: string;
++ /**
++ * Dynamic policy aliases that resolve to this model.
++ */
++ aliases?: (
++ "openvibe" | "openvibe/auto" | "openvibe/economy" | "openvibe/balanced" | "openvibe/fast" | "openvibe/private"
++ )[];
++ /**
++ * Creator and underlying model family, e.g. `qwen3`.
++ */
++ family?: string;
++ /**
++ * How the weights were produced.
++ */
++ variant?: "original" | "abliterated" | "finetuned" | "merged" | "lora";
++ /**
++ * Quantization label, e.g. `q4_k_m`.
++ */
++ quant?: string;
++ /**
++ * Artifact revision or commit hash.
++ */
++ revision?: string;
++ /**
++ * SHA-256 of the weight file or manifest.
++ */
++ sha256?: string;
++ /**
++ * SPDX license identifier or custom license name.
++ */
++ license?: string;
++ /**
++ * Maximum context length in tokens.
++ */
++ context?: number;
++ capabilities?: {
++ tools?: boolean;
++ json?: boolean;
++ vision?: boolean;
++ stream?: boolean;
++ };
++ /**
++ * Input/output modalities the model supports.
++ */
++ modalities?: ("text" | "image" | "audio" | "video")[];
++ /**
++ * Customer retail price, immutable rate version and nano-USD per-million-token component rates.
++ */
++ retail_rate?: {
++ version?: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ input_nano_usd_per_m?: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ cached_read_nano_usd_per_m?: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ cache_write_nano_usd_per_m?: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ output_nano_usd_per_m?: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ per_request_nano_usd?: string;
++ };
++ /**
++ * Whether the model can be chosen for new requests.
++ */
++ availability?: "available" | "limited" | "unavailable" | "deprecated";
metadata?: {};
created_at?: string | null;
updated_at?: string | null;
}
++/** [email protected] (owner: ai) */
++/**
++ * ai.model-artifact@1: one pinned revision/variant/quantization of a model family. Identified by an exact id (`<org>/<model>[:<quant>]@<revision>`), with its weight SHA-256, tokenizer revision, license and provenance.
++ */
++export interface AiModelArtifact {
++ /**
++ * Exact artifact id: `<org>/<model>[:<quant>]@<revision>`.
++ */
++ id: string;
++ /**
++ * Underlying model family, e.g. `qwen3`.
++ */
++ family: string;
++ /**
++ * How the weights were produced.
++ */
++ variant: "original" | "abliterated" | "finetuned" | "merged" | "lora";
++ /**
++ * Quantization label, e.g. `q4_k_m` or `fp16`.
++ */
++ quant: string;
++ /**
++ * Artifact revision or commit hash.
++ */
++ revision: string;
++ /**
++ * SHA-256 of the weight file or signed manifest.
++ */
++ sha256: string;
++ /**
++ * Tokenizer/chat-template revision used with this artifact.
++ */
++ tokenizer_revision?: string | null;
++ /**
++ * SPDX license identifier or custom license name.
++ */
++ license: string;
++ /**
++ * Canonical download or provenance URL.
++ */
++ source_url?: string;
++ /**
++ * Maximum context length in tokens.
++ */
++ context?: number;
++ created_at?: string;
++}
++
++/** [email protected] (owner: ai) */
++/**
++ * ai.inference-deployment@1: a loaded inference deployment for a pinned artifact. Describes where it runs, its trust tier, commercial rights, health, limits and the rate card that prices its supply cost.
++ */
++export interface AiInferenceDeployment {
++ /**
++ * Deployment id, unique within OpenVibe.AI.
++ */
++ id: string;
++ /**
++ * Exact artifact id this deployment loads.
++ */
++ artifact_id: string;
++ /**
++ * Deployment region or residency label.
++ */
++ region: string;
++ /**
++ * Who operates the deployment and what data may reach it.
++ */
++ trust_tier?: "first_party" | "owner_node" | "approved_wholesale" | "unverified";
++ /**
++ * Operator slug or `owner:<subject_id>` for a private node.
++ */
++ operator?: string;
++ /**
++ * Internal host identifier; never exposed to untrusted callers.
++ */
++ host?: string | null;
++ /**
++ * Service endpoint for this deployment (internal or partner).
++ */
++ endpoint?: string | null;
++ /**
++ * Current load/health state.
++ */
++ status: "warming" | "ready" | "full" | "degraded" | "offline";
++ loaded_at?: string | null;
++ limits?: {
++ max_concurrency?: number;
++ context_tokens?: number;
++ max_output_tokens?: number;
++ };
++ /**
++ * Reference to the supply rate card (platform.rate-card@1) that prices this deployment.
++ */
++ supply_rate_card_id?: string | null;
++ /**
++ * Commercial eligibility for paid managed routes.
++ */
++ provider_access?: "OWN_DEPLOYMENT" | "AUTHORIZED_WHOLESALE" | "CUSTOMER_BYOK" | "RESTRICTED" | "UNVERIFIED";
++ /**
++ * Resale authority recorded for this deployment.
++ */
++ commercial_rights?: "OWN_DEPLOYMENT" | "AUTHORIZED_WHOLESALE" | "CUSTOMER_BYOK" | "RESTRICTED" | "UNVERIFIED";
++ /**
++ * Retention/zero-retention policy label.
++ */
++ privacy_policy?: string | null;
++ health?: {
++ healthy?: boolean;
++ last_check_at?: string | null;
++ reason?: string | null;
++ };
++ updated_at?: string;
++}
++
++/** [email protected] (owner: ai) */
++/**
++ * ai.route-explain@1: why OpenVibe.AI chose a model/deployment for a request. Lists the chosen candidate, rejected candidates with reasons, the objective (policy), the pre-flight cost estimate and the final actual cost. Money fields are integer nano-USD, as strings.
++ */
++export interface AiRouteExplain {
++ /**
++ * Exact id of the model/deployment that answered.
++ */
++ chosen: string;
++ rejected: {
++ /**
++ * Exact id of the rejected candidate.
++ */
++ id: string;
++ /**
++ * Human-readable exclusion reason, without secrets or internal host IPs.
++ */
++ reason: string;
++ }[];
++ /**
++ * Routing policy used for this request.
++ */
++ objective: "cheapest" | "balanced" | "fastest" | "best" | "private" | "exact";
++ estimate: {
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ charge_nano_usd: string;
++ input_tokens?: number;
++ cached_read_tokens?: number;
++ cache_write_tokens?: number;
++ output_tokens?: number;
++ };
++ actual?: {
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ charge_nano_usd?: string;
++ input_tokens?: number;
++ cached_read_tokens?: number;
++ cache_write_tokens?: number;
++ output_tokens?: number;
++ };
++ /**
++ * Name of the policy/profile that produced the choice.
++ */
++ policy?: string;
++ /**
++ * If the first choice could not be used, why the chosen deployment was selected instead.
++ */
++ fallback_reason?: string | null;
++}
++
++/** [email protected] (owner: ai) */
++/**
++ * ai.model-list@1: paginated list of model catalog entries returned by GET /v1/models.
++ */
++export interface AiModelList {
++ models: AiModel[];
++ /**
++ * Number of models in this response.
++ */
++ count: number;
++ /**
++ * Opaque cursor for the next page.
++ */
++ cursor?: string | null;
++}
++
++/** [email protected] (owner: ai) */
++/**
++ * ai.model-read-result@1: response from GET /v1/models (a list) or GET /v1/models/:id (a single model).
++ */
++export type AiModelReadResult = AiModelList | AiModel;
++
/** [email protected] (owner: ai) */
/**
* ai.provider-manage-request@1: bodies of ai.provider.manage on OpenVibe.AI. POST /api/v1/providers creates or replaces a provider (key required) and PATCH /api/v1/providers/:key changes one (fields left out keep their value); a body carrying api_key or secret is refused (422: send secret_ref env:NAME, never a secret value). POST /api/v1/models (provider_key and model_key required) and PATCH /api/v1/models/:provider/:model upsert a model. POST /api/v1/quotas upserts a quota (scope_type and window required; scope_id too unless global; the same scope, window and workflow_prefix is one quota). POST /api/v1/providers/:key/disable|enable|reset and DELETE /api/v1/cache[?workflow=] take no body. Unknown fields are ignored.
@@@ -164321,6 +164589,253 @@ export interface BillingUsageRecordResult {
};
}
++/** [email protected] (owner: billing) */
++/**
++ * billing.credit-hold@1: a reservation against an account's available inference credit. Holds are created before a request runs, may be extended while streaming, and are settled or released exactly once. Money fields are integer nano-USD, as strings.
++ */
++export interface BillingCreditHold {
++ /**
++ * ULID hold id.
++ */
++ hold_id: string;
++ subject: SubjectRef;
++ project_id: string;
++ /**
++ * Developer credential the hold is scoped to, if any.
++ */
++ credential_id?: string | null;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ held_nano_usd: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ available_nano_usd: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ max_nano_usd?: string;
++ /**
++ * Immutable retail rate-card version the hold was priced against.
++ */
++ rate_version?: string;
++ /**
++ * Opaque fingerprint used to dedupe the same idempotency key.
++ */
++ request_fingerprint?: string;
++ lease_expires_at: string;
++ status?: "active" | "extended" | "settled" | "released" | "expired";
++ created_at?: string;
++ /**
++ * Which metered service spends the credit (D9): ai, run, ... Defaults to ai.
++ */
++ service?: string;
++}
++
++/** [email protected] (owner: billing) */
++/**
++ * billing.credit-settlement@1: the final charge for one inference hold. Carries the measured usage, the retail rate version applied, the supply-side cost, whether the route was BYOK, and any attempts/fallbacks. Money fields are integer nano-USD, as strings.
++ */
++export interface BillingCreditSettlement {
++ settlement_id: string;
++ hold_id: string;
++ subject: SubjectRef;
++ project_id: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ charge_nano_usd: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ released_nano_usd: string;
++ journal_id: string;
++ usage?: {
++ input_tokens: number;
++ cached_read_tokens: number;
++ cache_write_tokens: number;
++ output_tokens: number;
++ };
++ retail?: {
++ version: string;
++ nano_usd_per_m: {
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ input: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ cached_read: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ cache_write: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ output: string;
++ };
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ per_request_nano_usd: string;
++ };
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ supply_cost_nano_usd?: string;
++ /**
++ * True when the caller supplied their own provider key; retail charge is 0.
++ */
++ byok?: boolean;
++ attempts?: {
++ deployment_id: string;
++ provider_access?: "OWN_DEPLOYMENT" | "AUTHORIZED_WHOLESALE" | "CUSTOMER_BYOK" | "RESTRICTED" | "UNVERIFIED";
++ started_at: string;
++ ended_at: string;
++ error_code?: string | null;
++ }[];
++ created_at?: string;
++ /**
++ * Usage beyond the hold and the free balance: owed, not collected (nano-USD, D1).
++ */
++ shortfall_nano_usd?: string;
++}
++
++/** [email protected] (owner: billing) */
++/**
++ * billing.credit-hold-create-request@1: body of POST /internal/credits/holds. Reserves inference credit up to max_nano_usd against a (subject, project_id) account, priced at the given immutable rate version.
++ */
++export interface BillingCreditHoldCreateRequest {
++ idempotency_key: string;
++ subject: SubjectRef;
++ project_id: string;
++ credential_id?: string | null;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ max_nano_usd: string;
++ rate_version: string;
++ request_fingerprint: string;
++ /**
++ * Which metered service spends the credit (D9): ai, run, ... Defaults to ai.
++ */
++ service?: string;
++}
++
++/** [email protected] (owner: billing) */
++/**
++ * billing.credit-hold-create-result@1: response from POST /internal/credits/holds. Returns the created or replayed hold.
++ */
++export interface BillingCreditHoldCreateResult {
++ hold_id: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ held_nano_usd: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ available_nano_usd: string;
++ lease_expires_at: string;
++}
++
++/** [email protected] (owner: billing) */
++/**
++ * billing.credit-hold-extend-request@1: body of POST /internal/credits/holds/:id/extend. Grows an active hold by add_nano_usd.
++ */
++export interface BillingCreditHoldExtendRequest {
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ add_nano_usd: string;
++}
++
++/** [email protected] (owner: billing) */
++/**
++ * billing.credit-hold-settle-request@1: body of POST /internal/credits/holds/:id/settle. Idempotent: the same key and fingerprint return the same settlement.
++ */
++export interface BillingCreditHoldSettleRequest {
++ idempotency_key: string;
++ usage: {
++ input_tokens: number;
++ cached_read_tokens: number;
++ cache_write_tokens: number;
++ output_tokens: number;
++ };
++ retail: {
++ version: string;
++ nano_usd_per_m: {
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ input: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ cached_read: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ cache_write: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ output: string;
++ };
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ per_request_nano_usd: string;
++ };
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ supply_cost_nano_usd: string;
++ byok: boolean;
++ attempts: {
++ deployment_id: string;
++ provider_access?: "OWN_DEPLOYMENT" | "AUTHORIZED_WHOLESALE" | "CUSTOMER_BYOK" | "RESTRICTED" | "UNVERIFIED";
++ started_at: string;
++ ended_at: string;
++ error_code?: string | null;
++ }[];
++}
++
++/** [email protected] (owner: billing) */
++/**
++ * billing.credit-hold-settle-result@1: response from POST /internal/credits/holds/:id/settle.
++ */
++export interface BillingCreditHoldSettleResult {
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ charge_nano_usd: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ released_nano_usd: string;
++ journal_id: string;
++ /**
++ * Usage beyond the hold and the free balance: owed, not collected (nano-USD, D1).
++ */
++ shortfall_nano_usd?: string;
++}
++
++/** [email protected] (owner: billing) */
++/**
++ * billing.credit-hold-release-result@1: response from POST /internal/credits/holds/:id/release.
++ */
++export interface BillingCreditHoldReleaseResult {
++ hold_id: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ released_nano_usd: string;
++}
++
/** [email protected] (owner: deals) */
/**
* deals.watch.matched v1 (OpenVibe.Deals server/domain/watches.js onObservation). A person's keyword, product or price-below watch matched a fresh observation of an active offer, inside the transaction that recorded the observation. At most one event per (watch, observation); a keyword/product watch notifies once per offer, a price-below watch again only for a lower price. Saved searches (kind search) never notify. Envelope: subject { type: watch, id: <watch_id> }, visibility internal, priority important, actor service:deals. OpenVibe.Network turns it into a DEAL_WATCH_MATCH notification for payload.recipient (server/notifications/events-consumer.js). A price is the decimal exactly as stated, never inferred; null when the observation stated none.
@@@ -202054,14 +202569,22 @@ export interface PlacementPlan {
signature: string;
}
--/** [email protected] (owner: network) */
++/** [email protected] (owner: network) */
/**
-- * One provider price with its free allowance, in the provider's real billing unit, with effective dates and where it was checked (roadmap WS-Z3 task 5). Changed only by review; never written by an AI on its own.
++ * One provider price with its free allowance, in the provider's real billing unit, with effective dates and where it was checked (roadmap WS-Z3 task 5). v1.1 adds optional `side` (supply|retail), `version` and `effective_to`; existing documents stay valid. Changed only by review; never written by an AI on its own.
*/
export interface RateCard {
id: string;
provider: string;
adapter?: string;
++ /**
++ * Whether this card prices what OpenVibe pays (supply) or what the customer pays (retail).
++ */
++ side?: "supply" | "retail";
++ /**
++ * Immutable version of this card; settlements reference it so historical prices survive updates.
++ */
++ version?: string;
/**
* e.g. queue-operation-64kb, request, gib-delivered, message, gb-month, class-a-op
*/
@@@ -202072,6 +202595,10 @@ export interface RateCard {
reset_period: "day" | "month" | "none";
region?: string;
effective_from: string;
++ /**
++ * Inclusive end date; null or absent means open-ended.
++ */
++ effective_to?: string;
effective_until?: string;
source: string;
verified_at: string;
@@@ -284541,3 +285068,33 @@ export interface TodayAgenda {
}[];
follows_unavailable: boolean;
}
++
++/** [email protected] (owner: billing) */
++/**
++ * billing.credit-sandbox-grant-request@1: POST /internal/credits/sandbox/grants, synthetic credit for tests and staging. Billing answers only when BILLING_CREDITS_SANDBOX=1; the grant is a test transaction (D9).
++ */
++export interface BillingCreditSandboxGrantRequest {
++ subject: SubjectRef;
++ project_id: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ amount_nano_usd: string;
++ reason?: string;
++}
++
++/** [email protected] (owner: billing) */
++/**
++ * billing.credit-sandbox-grant-result@1: the grant and the balance after it.
++ */
++export interface BillingCreditSandboxGrantResult {
++ grant_id: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ amount_nano_usd: string;
++ /**
++ * Integer nano-USD (1e-9 USD), stored as a string.
++ */
++ balance_nano_usd: string;
++}
ddiff --git a/manifests/capabilities/ai.inference.create.json b/manifests/capabilities/ai.inference.create.json
nnew file mode 100644
iindex 00000000..9e70ecce
---- /dev/null
++++ b/manifests/capabilities/ai.inference.create.json
@@@ -0,0 +1,27 @@
++{
++ "id": "ai.inference.create",
++ "version": "1.0",
++ "owner": "ai",
++ "status": "active",
++ "visibility": "public",
++ "description": "Create developer-app inference requests (D8): POST /v1/chat/completions and POST /v1/messages. Grantable to developer apps; the token's project_id scopes quota and spend. ai.run.create stays first-party for workflows and runs.",
++ "inputSchema": "ai.run-request@1",
++ "outputSchema": "ai.run@1",
++ "permissions": [
++ "ai:inference"
++ ],
++ "resourceConstraints": [
++ "project"
++ ],
++ "quotaClass": "ai-inference",
++ "events": [
++ "ai.run.queued",
++ "ai.run.succeeded",
++ "ai.run.failed",
++ "ai.run.cached"
++ ],
++ "implementedBy": [
++ "POST /v1/chat/completions",
++ "POST /v1/messages"
++ ]
++}
ddiff --git a/manifests/capabilities/ai.model.read.json b/manifests/capabilities/ai.model.read.json
nnew file mode 100644
iindex 00000000..aa115d97
---- /dev/null
++++ b/manifests/capabilities/ai.model.read.json
@@@ -0,0 +1,22 @@
++{
++ "id": "ai.model.read",
++ "version": "1.0",
++ "owner": "ai",
++ "status": "active",
++ "visibility": "public",
++ "description": "Read the public model catalog: GET /v1/models returns a list (ai.model-list@1) and GET /v1/models/:id returns one catalog entry (ai.model@1). Available to developer apps and anonymous callers.",
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "ai.model-read-result@1",
++ "permissions": [
++ "ai:catalog:read"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "ai-read",
++ "events": [],
++ "implementedBy": [
++ "GET /v1/models",
++ "GET /v1/models/:id"
++ ]
++}
ddiff --git a/manifests/capabilities/billing.credit.hold.create.json b/manifests/capabilities/billing.credit.hold.create.json
nnew file mode 100644
iindex 00000000..8185ba71
---- /dev/null
++++ b/manifests/capabilities/billing.credit.hold.create.json
@@@ -0,0 +1,21 @@
++{
++ "id": "billing.credit.hold.create",
++ "version": "1.0",
++ "owner": "billing",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Create an inference credit hold (D2): POST /internal/credits/holds. Service-authenticated; reserves up to max_nano_usd against a (subject, project_id) account.",
++ "inputSchema": "billing.credit-hold-create-request@1",
++ "outputSchema": "billing.credit-hold-create-result@1",
++ "permissions": [
++ "billing:credit:hold"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "billing-credit",
++ "events": [],
++ "implementedBy": [
++ "POST /internal/credits/holds"
++ ]
++}
ddiff --git a/manifests/capabilities/billing.credit.hold.extend.json b/manifests/capabilities/billing.credit.hold.extend.json
nnew file mode 100644
iindex 00000000..aed143e5
---- /dev/null
++++ b/manifests/capabilities/billing.credit.hold.extend.json
@@@ -0,0 +1,21 @@
++{
++ "id": "billing.credit.hold.extend",
++ "version": "1.0",
++ "owner": "billing",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Extend an active inference hold (D2): POST /internal/credits/holds/:id/extend. Grows the held amount mid-stream; returns 402 billing.insufficient_funds if the balance is short.",
++ "inputSchema": "billing.credit-hold-extend-request@1",
++ "outputSchema": "billing.credit-hold-create-result@1",
++ "permissions": [
++ "billing:credit:hold"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "billing-credit",
++ "events": [],
++ "implementedBy": [
++ "POST /internal/credits/holds/:id/extend"
++ ]
++}
ddiff --git a/manifests/capabilities/billing.credit.hold.release.json b/manifests/capabilities/billing.credit.hold.release.json
nnew file mode 100644
iindex 00000000..0332739f
---- /dev/null
++++ b/manifests/capabilities/billing.credit.hold.release.json
@@@ -0,0 +1,21 @@
++{
++ "id": "billing.credit.hold.release",
++ "version": "1.0",
++ "owner": "billing",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Release a credit hold (D2): POST /internal/credits/holds/:id/release. Releases the remaining held amount with nothing charged.",
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "billing.credit-hold-release-result@1",
++ "permissions": [
++ "billing:credit:hold"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "billing-credit",
++ "events": [],
++ "implementedBy": [
++ "POST /internal/credits/holds/:id/release"
++ ]
++}
ddiff --git a/manifests/capabilities/billing.credit.hold.settle.json b/manifests/capabilities/billing.credit.hold.settle.json
nnew file mode 100644
iindex 00000000..ad38de78
---- /dev/null
++++ b/manifests/capabilities/billing.credit.hold.settle.json
@@@ -0,0 +1,21 @@
++{
++ "id": "billing.credit.hold.settle",
++ "version": "1.0",
++ "owner": "billing",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Settle a credit hold (D2): POST /internal/credits/holds/:id/settle. Records measured usage and the immutable retail rate version, charges the project account and releases the unused hold. Idempotent: replays return the same settlement.",
++ "inputSchema": "billing.credit-hold-settle-request@1",
++ "outputSchema": "billing.credit-hold-settle-result@1",
++ "permissions": [
++ "billing:credit:settle"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "billing-credit",
++ "events": [],
++ "implementedBy": [
++ "POST /internal/credits/holds/:id/settle"
++ ]
++}
ddiff --git a/manifests/capabilities/billing.credit.sandbox.grant.json b/manifests/capabilities/billing.credit.sandbox.grant.json
nnew file mode 100644
iindex 00000000..b9715beb
---- /dev/null
++++ b/manifests/capabilities/billing.credit.sandbox.grant.json
@@@ -0,0 +1,21 @@
++{
++ "id": "billing.credit.sandbox.grant",
++ "version": "1.0",
++ "owner": "billing",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Grant synthetic credit to a (subject, project) for tests and staging (D9): POST /internal/credits/sandbox/grants. Refused unless BILLING_CREDITS_SANDBOX=1; a test transaction, never money.",
++ "inputSchema": "billing.credit-sandbox-grant-request@1",
++ "outputSchema": "billing.credit-sandbox-grant-result@1",
++ "permissions": [
++ "billing:credit:sandbox"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "billing-credit",
++ "events": [],
++ "implementedBy": [
++ "POST /internal/credits/sandbox/grants"
++ ]
++}
\\ No newline at end of file
ddiff --git a/manifests/services/ai.json b/manifests/services/ai.json
iindex c25e9665..08c133ee 100644
---- a/manifests/services/ai.json
++++ b/manifests/services/ai.json
@@@ -22,6 +22,8 @@
"capabilities": [
"ai.app.run",
"ai.credential.manage",
++ "ai.inference.create",
++ "ai.model.read",
"ai.provider.manage",
"ai.quota.attribution.manage",
"ai.run.create",
ddiff --git a/manifests/services/billing.json b/manifests/services/billing.json
iindex dbe4ce3b..1807301b 100644
---- a/manifests/services/billing.json
++++ b/manifests/services/billing.json
@@@ -30,7 +30,12 @@
"billing.entitlement.check",
"billing.stats.read",
"billing.ledger.admin",
-- "billing.usage.record"
++ "billing.usage.record",
++ "billing.credit.hold.create",
++ "billing.credit.hold.extend",
++ "billing.credit.hold.settle",
++ "billing.credit.hold.release",
++ "billing.credit.sandbox.grant"
],
"eventsProduced": [
"billing.transaction.settled",
@@@ -85,7 +90,7 @@
],
"window": "automatic only while ovhost waits for readiness after the restart (30 s in host.example.json); manual afterwards, as long as no blocker applies",
"blockers": {
-- "none": "the schema code only adds (CREATE … IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
++ "none": "the schema code only adds (CREATE \u2026 IF NOT EXISTS, ADD COLUMN; no DROP, RENAME or table rebuild), which an older release ignores"
}
},
"contracts": {
ddiff --git a/package.json b/package.json
iindex 3fd2c01b..62fac850 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.283.0",
++ "version": "0.284.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",