openvibers/openvibe.contracts Commits

Add agent token claims and confirmation lifecycle contracts (#23)

OP OpenVibers committed 4 Oct 2026 · 331d9e39a0 · 54 files, +982 −75

local checks ✓ 3/3 · CI ✓ 3/3 · review SHIP by DeepSeek harness (QUALITY=high)

https://github.com/OpenVibers/OpenVibe.Contracts/pull/23

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 65c41560..e97406cf 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,35 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
++## 0.89.0 — 2026-10-03
++
++**Agent tokens and confirmations** (plan T2 step 9; OpenVibe.Network `docs/t2-projects-and-grants.md` §2 gaps 1–3,
++§4 and §5). These are the contracts that Network WS-Z2 slices 7–9 wait on. All of it is additive.
++- `identity.service-token-claims` 1.3.0: `sub` also accepts `agent:agt_<ULID>` (`ids.principalSub({ type: 'agent' })`)
++ and `actor_type` gains `agent`. An agent token must carry `project_id`, `env` and `on_behalf_of` (the owner, `usr_`).
++ It may carry `cap_confirm` (capabilities held in confirm mode: each use needs an approved
++ `network.confirmation-request@1`, and they never appear in `cap`, so a receiver that does not know agents refuses
++ them) and `act` (RFC 8693 actor claim: `{ sub: 'svc:<slug>' | 'app:app_<ULID>' }`, the host, nothing else).
++ `if`/`then` rules tie an `agent:` sub to `actor_type: agent` in both directions. Service, app, mod and node
++ tokens validate as before.
++ Fixtures: valid `agent-service-host` and `agent-app-host-auto-only`; invalid `agent-without-owner`,
++ `agent-sub-as-service`, `agent-actor-with-service-sub` and `agent-bad-actor-claim`.
++- New capability `network.confirmation.manage` (planned, first-party, listed by `manifests/services/network.json`). It is
++ the owning service's side of a confirmation: create one (`POST /internal/confirmations`), read it, consume it once
++ with the action's digest, or cancel it. The owner's inbox (`/api/v1/confirmations`: list, approve, deny) runs on the
++ owner's own session, and no token holds it. The request and response bodies are Network-local until the routes are
++ built.
++- New event `network.confirmation.changed@1` (planned, produced by Network), the shape of Network's proposal
++ (`docs/contracts-proposal/network.confirmation.changed.v1.json`). Payload: `confirmation_id` (`cnf_`), `agent_id`
++ (`agt_`), `project_id`, `capability`, `audience` (the owning service), `state` (`pending | approved | denied | expired
++ | cancelled`, the names of `network.confirmation-request@1`) and `change` (`created | approved | denied | expired |
++ cancelled | used`), and optional `standing_rule`, `rule_id`, `cancel_reason`, `expires_at` and `changed_at`. `created`
++ leaves the state pending, or approved when a standing rule covers it; `used` leaves it approved; every other change
++ names its state. `cancel_reason` is required on cancelled and absent otherwise. The event never carries `summary` or
++ `details`. Fixtures: valid `requested`, `approved-by-owner`, `approved-by-standing-rule`, `used`, `cancelled` and
++ `expired`; invalid `requested-state`, `cancelled-without-reason`, `used-not-approved`, `missing-audience` and
++ `with-summary`.
++
## 0.88.0 — 2026-10-03
**The Fabric ADR and the user-owned trust rule** (plan T1 step 3; T14 S2, the Contracts half). ADR-046 "The universal
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex a47dd888..24dbe688 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -23,7 +23,7 @@
},
{
"id": "identity.service-token-claims",
-- "version": "1.2.0",
++ "version": "1.3.0",
"owner": "network",
"schema": "identity/service-token-claims.v1.json",
"visibility": "public",
@@@ -5552,6 +5552,16 @@
"status": "active",
"adr": "ADR-034"
},
++ {
++ "id": "network.confirmation.changed",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "events/payloads/network.confirmation.changed.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "planned",
++ "adr": "ADR-034"
++ },
{
"id": "zone.object-zone",
"version": "1.0.0",
ddiff --git a/contracts/events/payloads/network.confirmation.changed.v1.json b/contracts/events/payloads/network.confirmation.changed.v1.json
nnew file mode 100644
iindex 00000000..14a580ab
---- /dev/null
++++ b/contracts/events/payloads/network.confirmation.changed.v1.json
@@@ -0,0 +1,246 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/network.confirmation.changed.v1.json",
++ "title": "NetworkConfirmationChangedPayload",
++ "description": "network.confirmation.changed v1 (OpenVibe.Network; roadmap WS-Z2, docs/t2-projects-and-grants.md section 5; shape from OpenVibe.Network docs/contracts-proposal/network.confirmation.changed.v1.json). A confirmation request (network.confirmation-request@1) an agent raised for a sensitive capability was created, approved, denied, expired, cancelled or used. state is the state after the change, named as in network.confirmation-request@1: created leaves it pending (or approved when a standing rule covers it, with rule_id), used leaves it approved, and every other change names its state. A cancelled change says why in cancel_reason. Written to Network's outbox in the transaction that changes it, beside an audit row. Never carries the summary or details, which may hold message text: a reader that needs them asks Network with its own authority. Envelope: subject { type: confirmation, id: cnf_… }, visibility internal, on_behalf_of the owner, actor the owner who decided (or the system for an expiry, a cascade or a standing rule).",
++ "type": "object",
++ "required": [
++ "confirmation_id",
++ "agent_id",
++ "project_id",
++ "capability",
++ "audience",
++ "state",
++ "change"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "confirmation_id": {
++ "type": "string",
++ "pattern": "^cnf_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "agent_id": {
++ "type": "string",
++ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The agent that asked."
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The agent's developer project."
++ },
++ "capability": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$"
++ },
++ "audience": {
++ "type": "string",
++ "pattern": "^openvibe\\.[a-z][a-z0-9-]{0,63}$",
++ "description": "The owning service of the capability: the only one that may consume or cancel the confirmation."
++ },
++ "state": {
++ "type": "string",
++ "enum": [
++ "pending",
++ "approved",
++ "denied",
++ "expired",
++ "cancelled"
++ ],
++ "description": "The state after the change. A use leaves it approved and sets used_at."
++ },
++ "change": {
++ "type": "string",
++ "enum": [
++ "created",
++ "approved",
++ "denied",
++ "expired",
++ "cancelled",
++ "used"
++ ]
++ },
++ "standing_rule": {
++ "type": "string",
++ "enum": [
++ "once",
++ "session",
++ "until",
++ "always"
++ ],
++ "description": "On approved: the rule the owner chose (once creates none)."
++ },
++ "rule_id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "On created through a standing rule, or on approved with a session, until or always rule: that rule."
++ },
++ "cancel_reason": {
++ "type": "string",
++ "enum": [
++ "service",
++ "agent_paused",
++ "agent_revoked",
++ "grant_revoked",
++ "grant_expired",
++ "beyond_host",
++ "member_removed",
++ "account_erased",
++ "app_revoked",
++ "project_archived"
++ ],
++ "description": "On cancelled: what took the authority away."
++ },
++ "expires_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "changed_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "change": {
++ "const": "created"
++ }
++ },
++ "required": [
++ "change"
++ ]
++ },
++ "then": {
++ "properties": {
++ "state": {
++ "enum": [
++ "pending",
++ "approved"
++ ]
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "change": {
++ "const": "used"
++ }
++ },
++ "required": [
++ "change"
++ ]
++ },
++ "then": {
++ "properties": {
++ "state": {
++ "const": "approved"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "change": {
++ "const": "approved"
++ }
++ },
++ "required": [
++ "change"
++ ]
++ },
++ "then": {
++ "properties": {
++ "state": {
++ "const": "approved"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "change": {
++ "const": "denied"
++ }
++ },
++ "required": [
++ "change"
++ ]
++ },
++ "then": {
++ "properties": {
++ "state": {
++ "const": "denied"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "change": {
++ "const": "expired"
++ }
++ },
++ "required": [
++ "change"
++ ]
++ },
++ "then": {
++ "properties": {
++ "state": {
++ "const": "expired"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "change": {
++ "const": "cancelled"
++ }
++ },
++ "required": [
++ "change"
++ ]
++ },
++ "then": {
++ "properties": {
++ "state": {
++ "const": "cancelled"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "cancelled"
++ }
++ },
++ "required": [
++ "state"
++ ]
++ },
++ "then": {
++ "required": [
++ "cancel_reason"
++ ]
++ },
++ "else": {
++ "not": {
++ "required": [
++ "cancel_reason"
++ ]
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/identity/service-token-claims.v1.json b/contracts/identity/service-token-claims.v1.json
iindex ac560cea..7d6414e1 100644
---- a/contracts/identity/service-token-claims.v1.json
++++ b/contracts/identity/service-token-claims.v1.json
@@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/identity/service-token-claims.v1.json",
"title": "ServiceTokenClaims",
-- "description": "Claims of a short-lived RS256 client-credentials token issued by OpenVibe.Network to a service, app, mod or node principal. Replaces X-Internal-Key. App tokens (actor_type app) also carry project_id and env; receivers refuse env=sandbox unless they opted in.",
++ "description": "Claims of a short-lived RS256 client-credentials token issued by OpenVibe.Network to a service, app, mod, node or agent principal. Replaces X-Internal-Key. App tokens (actor_type app) also carry project_id and env; receivers refuse env=sandbox unless they opted in. Agent tokens (actor_type agent, sub agent:agt_<ULID>) are minted for the agent's host and also carry project_id, env and on_behalf_of (the owner), and optionally cap_confirm and act.",
"type": "object",
"required": [
"iss",
@@@ -23,14 +23,15 @@
},
"sub": {
"type": "string",
-- "pattern": "^(svc:[a-z][a-z0-9-]{1,39}|app:app_[0-9A-HJKMNP-TV-Z]{26}|mod:mod_[0-9A-HJKMNP-TV-Z]{26}|node:nod_[0-9A-HJKMNP-TV-Z]{26})$"
++ "pattern": "^(svc:[a-z][a-z0-9-]{1,39}|app:app_[0-9A-HJKMNP-TV-Z]{26}|mod:mod_[0-9A-HJKMNP-TV-Z]{26}|node:nod_[0-9A-HJKMNP-TV-Z]{26}|agent:agt_[0-9A-HJKMNP-TV-Z]{26})$"
},
"actor_type": {
"enum": [
"service",
"app",
"mod",
-- "node"
++ "node",
++ "agent"
]
},
"aud": {
@@@ -49,6 +50,14 @@
},
"description": "Granted capability ids; a trailing .* grants a family."
},
++ "cap_confirm": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$"
++ },
++ "description": "Agent tokens: capabilities the agent holds in confirm mode. Each use needs an approved network.confirmation-request@1, presented as OpenVibe-Confirmation: cnf_<ULID> and consumed by the owning service. Never also in cap, so a receiver that does not know agents refuses those actions."
++ },
"ns": {
"type": "array",
"items": {
@@@ -69,19 +78,33 @@
"project_id": {
"type": "string",
"pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "Developer project of an app principal (ADR-014). Services key tenancy by it. Absent on first-party service tokens."
++ "description": "Developer project of an app or agent principal (ADR-014). Services key tenancy by it. Absent on first-party service tokens."
},
"env": {
"enum": [
"sandbox",
"production"
],
-- "description": "Environment of an app principal. A receiver MUST refuse env=sandbox (401 token.sandbox_refused) unless it opted in to sandbox tokens. Absent on first-party service tokens, which are production."
++ "description": "Environment of an app or agent principal. A receiver MUST refuse env=sandbox (401 token.sandbox_refused) unless it opted in to sandbox tokens. Absent on first-party service tokens, which are production."
},
"on_behalf_of": {
"type": "string",
"pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "The person who authorized an app through the authorization-code flow. Absent on client_credentials tokens."
++ "description": "The person who authorized an app through the authorization-code flow, or the owner an agent acts for (always present on agent tokens). Absent on client_credentials tokens."
++ },
++ "act": {
++ "type": "object",
++ "required": [
++ "sub"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "sub": {
++ "type": "string",
++ "pattern": "^(svc:[a-z][a-z0-9-]{1,39}|app:app_[0-9A-HJKMNP-TV-Z]{26})$"
++ }
++ },
++ "description": "Agent tokens: the host that runs the agent and the token was minted for (RFC 8693 actor claim), a service or an app."
}
},
"allOf": [
@@@ -102,6 +125,54 @@
"env"
]
}
++ },
++ {
++ "if": {
++ "properties": {
++ "actor_type": {
++ "const": "agent"
++ }
++ },
++ "required": [
++ "actor_type"
++ ]
++ },
++ "then": {
++ "required": [
++ "project_id",
++ "env",
++ "on_behalf_of"
++ ],
++ "properties": {
++ "sub": {
++ "type": "string",
++ "pattern": "^agent:agt_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "sub": {
++ "type": "string",
++ "pattern": "^agent:"
++ }
++ },
++ "required": [
++ "sub"
++ ]
++ },
++ "then": {
++ "required": [
++ "actor_type"
++ ],
++ "properties": {
++ "actor_type": {
++ "const": "agent"
++ }
++ }
++ }
}
]
}
ddiff --git a/fixtures/identity.service-token-claims/invalid/agent-actor-with-service-sub.json b/fixtures/identity.service-token-claims/invalid/agent-actor-with-service-sub.json
nnew file mode 100644
iindex 00000000..52ec972a
---- /dev/null
++++ b/fixtures/identity.service-token-claims/invalid/agent-actor-with-service-sub.json
@@@ -0,0 +1,17 @@
++{
++ "iss": "https://openvibe.network",
++ "sub": "svc:actor",
++ "actor_type": "agent",
++ "aud": [
++ "openvibe.chat"
++ ],
++ "cap": [
++ "chat.presence.read"
++ ],
++ "iat": 1790000000,
++ "exp": 1790000300,
++ "jti": "tok_agent005",
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "on_behalf_of": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/identity.service-token-claims/invalid/agent-bad-actor-claim.json b/fixtures/identity.service-token-claims/invalid/agent-bad-actor-claim.json
nnew file mode 100644
iindex 00000000..bf301c3d
---- /dev/null
++++ b/fixtures/identity.service-token-claims/invalid/agent-bad-actor-claim.json
@@@ -0,0 +1,20 @@
++{
++ "iss": "https://openvibe.network",
++ "sub": "agent:agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "actor_type": "agent",
++ "aud": [
++ "openvibe.chat"
++ ],
++ "cap": [
++ "chat.presence.read"
++ ],
++ "iat": 1790000000,
++ "exp": 1790000300,
++ "jti": "tok_agent006",
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "on_behalf_of": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "act": {
++ "sub": "agent:agt_01J0000000000000000000000Z"
++ }
++}
ddiff --git a/fixtures/identity.service-token-claims/invalid/agent-sub-as-service.json b/fixtures/identity.service-token-claims/invalid/agent-sub-as-service.json
nnew file mode 100644
iindex 00000000..8b4f107b
---- /dev/null
++++ b/fixtures/identity.service-token-claims/invalid/agent-sub-as-service.json
@@@ -0,0 +1,14 @@
++{
++ "iss": "https://openvibe.network",
++ "sub": "agent:agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "actor_type": "service",
++ "aud": [
++ "openvibe.chat"
++ ],
++ "cap": [
++ "chat.message.send"
++ ],
++ "iat": 1790000000,
++ "exp": 1790000300,
++ "jti": "tok_agent004"
++}
ddiff --git a/fixtures/identity.service-token-claims/invalid/agent-without-owner.json b/fixtures/identity.service-token-claims/invalid/agent-without-owner.json
nnew file mode 100644
iindex 00000000..47c90eaf
---- /dev/null
++++ b/fixtures/identity.service-token-claims/invalid/agent-without-owner.json
@@@ -0,0 +1,20 @@
++{
++ "iss": "https://openvibe.network",
++ "sub": "agent:agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "actor_type": "agent",
++ "aud": [
++ "openvibe.chat"
++ ],
++ "cap": [],
++ "cap_confirm": [
++ "chat.message.send"
++ ],
++ "iat": 1790000000,
++ "exp": 1790000300,
++ "jti": "tok_agent003",
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "act": {
++ "sub": "svc:actor"
++ }
++}
ddiff --git a/fixtures/identity.service-token-claims/valid/agent-app-host-auto-only.json b/fixtures/identity.service-token-claims/valid/agent-app-host-auto-only.json
nnew file mode 100644
iindex 00000000..5f5e8621
---- /dev/null
++++ b/fixtures/identity.service-token-claims/valid/agent-app-host-auto-only.json
@@@ -0,0 +1,24 @@
++{
++ "iss": "https://openvibe.network",
++ "sub": "agent:agt_01J0000000000000000000000Z",
++ "actor_type": "agent",
++ "aud": [
++ "openvibe.media"
++ ],
++ "cap": [
++ "media.object.read"
++ ],
++ "ns": [
++ "prj_01J0000000000000000000000Z",
++ "app.prj_01J0000000000000000000000Z.*"
++ ],
++ "iat": 1790000000,
++ "exp": 1790000300,
++ "jti": "tok_agent002",
++ "project_id": "prj_01J0000000000000000000000Z",
++ "env": "sandbox",
++ "on_behalf_of": "usr_01J0000000000000000000000Z",
++ "act": {
++ "sub": "app:app_01J0000000000000000000000Z"
++ }
++}
ddiff --git a/fixtures/identity.service-token-claims/valid/agent-service-host.json b/fixtures/identity.service-token-claims/valid/agent-service-host.json
nnew file mode 100644
iindex 00000000..3d038f57
---- /dev/null
++++ b/fixtures/identity.service-token-claims/valid/agent-service-host.json
@@@ -0,0 +1,26 @@
++{
++ "iss": "https://openvibe.network",
++ "sub": "agent:agt_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "actor_type": "agent",
++ "aud": [
++ "openvibe.chat"
++ ],
++ "cap": [
++ "chat.presence.read"
++ ],
++ "cap_confirm": [
++ "chat.message.send"
++ ],
++ "ns": [
++ "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++ ],
++ "iat": 1790000000,
++ "exp": 1790000300,
++ "jti": "tok_agent001",
++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "env": "production",
++ "on_behalf_of": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "act": {
++ "sub": "svc:actor"
++ }
++}
ddiff --git a/fixtures/network.confirmation.changed/invalid/cancelled-without-reason.json b/fixtures/network.confirmation.changed/invalid/cancelled-without-reason.json
nnew file mode 100644
iindex 00000000..113e79b1
---- /dev/null
++++ b/fixtures/network.confirmation.changed/invalid/cancelled-without-reason.json
@@@ -0,0 +1 @@
++{"confirmation_id":"cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ","agent_id":"agt_01JAB2C3D4E5F6G7H8J9K0MNPQ","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ","capability":"chat.message.send","audience":"openvibe.chat","state":"cancelled","change":"cancelled"}
ddiff --git a/fixtures/network.confirmation.changed/invalid/missing-audience.json b/fixtures/network.confirmation.changed/invalid/missing-audience.json
nnew file mode 100644
iindex 00000000..aae12567
---- /dev/null
++++ b/fixtures/network.confirmation.changed/invalid/missing-audience.json
@@@ -0,0 +1 @@
++{"confirmation_id":"cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ","agent_id":"agt_01JAB2C3D4E5F6G7H8J9K0MNPQ","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ","capability":"chat.message.send","state":"pending","change":"created"}
ddiff --git a/fixtures/network.confirmation.changed/invalid/requested-state.json b/fixtures/network.confirmation.changed/invalid/requested-state.json
nnew file mode 100644
iindex 00000000..c619005b
---- /dev/null
++++ b/fixtures/network.confirmation.changed/invalid/requested-state.json
@@@ -0,0 +1 @@
++{"confirmation_id":"cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ","agent_id":"agt_01JAB2C3D4E5F6G7H8J9K0MNPQ","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ","capability":"chat.message.send","audience":"openvibe.chat","state":"requested","change":"created"}
ddiff --git a/fixtures/network.confirmation.changed/invalid/used-not-approved.json b/fixtures/network.confirmation.changed/invalid/used-not-approved.json
nnew file mode 100644
iindex 00000000..7359c923
---- /dev/null
++++ b/fixtures/network.confirmation.changed/invalid/used-not-approved.json
@@@ -0,0 +1 @@
++{"confirmation_id":"cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ","agent_id":"agt_01JAB2C3D4E5F6G7H8J9K0MNPQ","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ","capability":"chat.message.send","audience":"openvibe.chat","state":"pending","change":"used"}
ddiff --git a/fixtures/network.confirmation.changed/invalid/with-summary.json b/fixtures/network.confirmation.changed/invalid/with-summary.json
nnew file mode 100644
iindex 00000000..59dcd2ea
---- /dev/null
++++ b/fixtures/network.confirmation.changed/invalid/with-summary.json
@@@ -0,0 +1 @@
++{"confirmation_id":"cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ","agent_id":"agt_01JAB2C3D4E5F6G7H8J9K0MNPQ","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ","capability":"chat.message.send","audience":"openvibe.chat","state":"pending","change":"created","summary":"Send 'see you at 8' to Sam"}
ddiff --git a/fixtures/network.confirmation.changed/valid/approved-by-owner.json b/fixtures/network.confirmation.changed/valid/approved-by-owner.json
nnew file mode 100644
iindex 00000000..791d2600
---- /dev/null
++++ b/fixtures/network.confirmation.changed/valid/approved-by-owner.json
@@@ -0,0 +1 @@
++{"confirmation_id":"cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ","agent_id":"agt_01JAB2C3D4E5F6G7H8J9K0MNPQ","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ","capability":"chat.message.send","audience":"openvibe.chat","state":"approved","change":"approved","standing_rule":"session","rule_id":7,"expires_at":"2026-10-03T12:15:00Z","changed_at":"2026-10-03T12:02:00Z"}
ddiff --git a/fixtures/network.confirmation.changed/valid/approved-by-standing-rule.json b/fixtures/network.confirmation.changed/valid/approved-by-standing-rule.json
nnew file mode 100644
iindex 00000000..9c69dcaf
---- /dev/null
++++ b/fixtures/network.confirmation.changed/valid/approved-by-standing-rule.json
@@@ -0,0 +1 @@
++{"confirmation_id":"cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ","agent_id":"agt_01JAB2C3D4E5F6G7H8J9K0MNPQ","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ","capability":"chat.message.send","audience":"openvibe.chat","state":"approved","change":"created","rule_id":7,"changed_at":"2026-10-03T12:05:00Z"}
ddiff --git a/fixtures/network.confirmation.changed/valid/cancelled.json b/fixtures/network.confirmation.changed/valid/cancelled.json
nnew file mode 100644
iindex 00000000..3060a6e0
---- /dev/null
++++ b/fixtures/network.confirmation.changed/valid/cancelled.json
@@@ -0,0 +1 @@
++{"confirmation_id":"cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ","agent_id":"agt_01JAB2C3D4E5F6G7H8J9K0MNPQ","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ","capability":"chat.message.send","audience":"openvibe.chat","state":"cancelled","change":"cancelled","cancel_reason":"agent_revoked","changed_at":"2026-10-03T12:07:00Z"}
ddiff --git a/fixtures/network.confirmation.changed/valid/expired.json b/fixtures/network.confirmation.changed/valid/expired.json
nnew file mode 100644
iindex 00000000..9f5172e2
---- /dev/null
++++ b/fixtures/network.confirmation.changed/valid/expired.json
@@@ -0,0 +1 @@
++{"confirmation_id":"cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ","agent_id":"agt_01JAB2C3D4E5F6G7H8J9K0MNPQ","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ","capability":"space.post.write","audience":"openvibe.space","state":"expired","change":"expired","changed_at":"2026-10-03T12:15:00Z"}
ddiff --git a/fixtures/network.confirmation.changed/valid/requested.json b/fixtures/network.confirmation.changed/valid/requested.json
nnew file mode 100644
iindex 00000000..891de6ec
---- /dev/null
++++ b/fixtures/network.confirmation.changed/valid/requested.json
@@@ -0,0 +1 @@
++{"confirmation_id":"cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ","agent_id":"agt_01JAB2C3D4E5F6G7H8J9K0MNPQ","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ","capability":"chat.message.send","audience":"openvibe.chat","state":"pending","change":"created","expires_at":"2026-10-03T12:15:00Z","changed_at":"2026-10-03T12:00:00Z"}
ddiff --git a/fixtures/network.confirmation.changed/valid/used.json b/fixtures/network.confirmation.changed/valid/used.json
nnew file mode 100644
iindex 00000000..70fa5d41
---- /dev/null
++++ b/fixtures/network.confirmation.changed/valid/used.json
@@@ -0,0 +1 @@
++{"confirmation_id":"cnf_01JAB2C3D4E5F6G7H8J9K0MNPQ","agent_id":"agt_01JAB2C3D4E5F6G7H8J9K0MNPQ","project_id":"prj_01JAB2C3D4E5F6G7H8J9K0MNPQ","capability":"network.integration.github.read","audience":"openvibe.network","state":"approved","change":"used","changed_at":"2026-10-03T12:06:00Z"}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex bdd6dd97..65b742b9 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -180,14 +180,14 @@
}
},
"identity.service-token-claims": {
-- "version": "1.2.0",
++ "version": "1.3.0",
"owner": "network",
"status": "active",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/identity/service-token-claims.v1.json",
"title": "ServiceTokenClaims",
-- "description": "Claims of a short-lived RS256 client-credentials token issued by OpenVibe.Network to a service, app, mod or node principal. Replaces X-Internal-Key. App tokens (actor_type app) also carry project_id and env; receivers refuse env=sandbox unless they opted in.",
++ "description": "Claims of a short-lived RS256 client-credentials token issued by OpenVibe.Network to a service, app, mod, node or agent principal. Replaces X-Internal-Key. App tokens (actor_type app) also carry project_id and env; receivers refuse env=sandbox unless they opted in. Agent tokens (actor_type agent, sub agent:agt_<ULID>) are minted for the agent's host and also carry project_id, env and on_behalf_of (the owner), and optionally cap_confirm and act.",
"type": "object",
"required": [
"iss",
@@@ -208,14 +208,15 @@
},
"sub": {
"type": "string",
-- "pattern": "^(svc:[a-z][a-z0-9-]{1,39}|app:app_[0-9A-HJKMNP-TV-Z]{26}|mod:mod_[0-9A-HJKMNP-TV-Z]{26}|node:nod_[0-9A-HJKMNP-TV-Z]{26})$"
++ "pattern": "^(svc:[a-z][a-z0-9-]{1,39}|app:app_[0-9A-HJKMNP-TV-Z]{26}|mod:mod_[0-9A-HJKMNP-TV-Z]{26}|node:nod_[0-9A-HJKMNP-TV-Z]{26}|agent:agt_[0-9A-HJKMNP-TV-Z]{26})$"
},
"actor_type": {
"enum": [
"service",
"app",
"mod",
-- "node"
++ "node",
++ "agent"
]
},
"aud": {
@@@ -234,6 +235,14 @@
},
"description": "Granted capability ids; a trailing .* grants a family."
},
++ "cap_confirm": {
++ "type": "array",
++ "items": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$"
++ },
++ "description": "Agent tokens: capabilities the agent holds in confirm mode. Each use needs an approved network.confirmation-request@1, presented as OpenVibe-Confirmation: cnf_<ULID> and consumed by the owning service. Never also in cap, so a receiver that does not know agents refuses those actions."
++ },
"ns": {
"type": "array",
"items": {
@@@ -254,19 +263,33 @@
"project_id": {
"type": "string",
"pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "Developer project of an app principal (ADR-014). Services key tenancy by it. Absent on first-party service tokens."
++ "description": "Developer project of an app or agent principal (ADR-014). Services key tenancy by it. Absent on first-party service tokens."
},
"env": {
"enum": [
"sandbox",
"production"
],
-- "description": "Environment of an app principal. A receiver MUST refuse env=sandbox (401 token.sandbox_refused) unless it opted in to sandbox tokens. Absent on first-party service tokens, which are production."
++ "description": "Environment of an app or agent principal. A receiver MUST refuse env=sandbox (401 token.sandbox_refused) unless it opted in to sandbox tokens. Absent on first-party service tokens, which are production."
},
"on_behalf_of": {
"type": "string",
"pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
-- "description": "The person who authorized an app through the authorization-code flow. Absent on client_credentials tokens."
++ "description": "The person who authorized an app through the authorization-code flow, or the owner an agent acts for (always present on agent tokens). Absent on client_credentials tokens."
++ },
++ "act": {
++ "type": "object",
++ "required": [
++ "sub"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "sub": {
++ "type": "string",
++ "pattern": "^(svc:[a-z][a-z0-9-]{1,39}|app:app_[0-9A-HJKMNP-TV-Z]{26})$"
++ }
++ },
++ "description": "Agent tokens: the host that runs the agent and the token was minted for (RFC 8693 actor claim), a service or an app."
}
},
"allOf": [
@@@ -287,6 +310,54 @@
"env"
]
}
++ },
++ {
++ "if": {
++ "properties": {
++ "actor_type": {
++ "const": "agent"
++ }
++ },
++ "required": [
++ "actor_type"
++ ]
++ },
++ "then": {
++ "required": [
++ "project_id",
++ "env",
++ "on_behalf_of"
++ ],
++ "properties": {
++ "sub": {
++ "type": "string",
++ "pattern": "^agent:agt_[0-9A-HJKMNP-TV-Z]{26}$"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "sub": {
++ "type": "string",
++ "pattern": "^agent:"
++ }
++ },
++ "required": [
++ "sub"
++ ]
++ },
++ "then": {
++ "required": [
++ "actor_type"
++ ],
++ "properties": {
++ "actor_type": {
++ "const": "agent"
++ }
++ }
++ }
}
]
}
@@@ -61835,6 +61906,257 @@
}
}
},
++ "network.confirmation.changed": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "planned",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/network.confirmation.changed.v1.json",
++ "title": "NetworkConfirmationChangedPayload",
++ "description": "network.confirmation.changed v1 (OpenVibe.Network; roadmap WS-Z2, docs/t2-projects-and-grants.md section 5; shape from OpenVibe.Network docs/contracts-proposal/network.confirmation.changed.v1.json). A confirmation request (network.confirmation-request@1) an agent raised for a sensitive capability was created, approved, denied, expired, cancelled or used. state is the state after the change, named as in network.confirmation-request@1: created leaves it pending (or approved when a standing rule covers it, with rule_id), used leaves it approved, and every other change names its state. A cancelled change says why in cancel_reason. Written to Network's outbox in the transaction that changes it, beside an audit row. Never carries the summary or details, which may hold message text: a reader that needs them asks Network with its own authority. Envelope: subject { type: confirmation, id: cnf_… }, visibility internal, on_behalf_of the owner, actor the owner who decided (or the system for an expiry, a cascade or a standing rule).",
++ "type": "object",
++ "required": [
++ "confirmation_id",
++ "agent_id",
++ "project_id",
++ "capability",
++ "audience",
++ "state",
++ "change"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "confirmation_id": {
++ "type": "string",
++ "pattern": "^cnf_[0-9A-HJKMNP-TV-Z]{26}$"
++ },
++ "agent_id": {
++ "type": "string",
++ "pattern": "^agt_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The agent that asked."
++ },
++ "project_id": {
++ "type": "string",
++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
++ "description": "The agent's developer project."
++ },
++ "capability": {
++ "type": "string",
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)+$"
++ },
++ "audience": {
++ "type": "string",
++ "pattern": "^openvibe\\.[a-z][a-z0-9-]{0,63}$",
++ "description": "The owning service of the capability: the only one that may consume or cancel the confirmation."
++ },
++ "state": {
++ "type": "string",
++ "enum": [
++ "pending",
++ "approved",
++ "denied",
++ "expired",
++ "cancelled"
++ ],
++ "description": "The state after the change. A use leaves it approved and sets used_at."
++ },
++ "change": {
++ "type": "string",
++ "enum": [
++ "created",
++ "approved",
++ "denied",
++ "expired",
++ "cancelled",
++ "used"
++ ]
++ },
++ "standing_rule": {
++ "type": "string",
++ "enum": [
++ "once",
++ "session",
++ "until",
++ "always"
++ ],
++ "description": "On approved: the rule the owner chose (once creates none)."
++ },
++ "rule_id": {
++ "type": "integer",
++ "minimum": 1,
++ "description": "On created through a standing rule, or on approved with a session, until or always rule: that rule."
++ },
++ "cancel_reason": {
++ "type": "string",
++ "enum": [
++ "service",
++ "agent_paused",
++ "agent_revoked",
++ "grant_revoked",
++ "grant_expired",
++ "beyond_host",
++ "member_removed",
++ "account_erased",
++ "app_revoked",
++ "project_archived"
++ ],
++ "description": "On cancelled: what took the authority away."
++ },
++ "expires_at": {
++ "type": "string",
++ "format": "date-time"
++ },
++ "changed_at": {
++ "type": "string",
++ "format": "date-time"
++ }
++ },
++ "allOf": [
++ {
++ "if": {
++ "properties": {
++ "change": {
++ "const": "created"
++ }
++ },
++ "required": [
++ "change"
++ ]
++ },
++ "then": {
++ "properties": {
++ "state": {
++ "enum": [
++ "pending",
++ "approved"
++ ]
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "change": {
++ "const": "used"
++ }
++ },
++ "required": [
++ "change"
++ ]
++ },
++ "then": {
++ "properties": {
++ "state": {
++ "const": "approved"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "change": {
++ "const": "approved"
++ }
++ },
++ "required": [
++ "change"
++ ]
++ },
++ "then": {
++ "properties": {
++ "state": {
++ "const": "approved"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "change": {
++ "const": "denied"
++ }
++ },
++ "required": [
++ "change"
++ ]
++ },
++ "then": {
++ "properties": {
++ "state": {
++ "const": "denied"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "change": {
++ "const": "expired"
++ }
++ },
++ "required": [
++ "change"
++ ]
++ },
++ "then": {
++ "properties": {
++ "state": {
++ "const": "expired"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "change": {
++ "const": "cancelled"
++ }
++ },
++ "required": [
++ "change"
++ ]
++ },
++ "then": {
++ "properties": {
++ "state": {
++ "const": "cancelled"
++ }
++ }
++ }
++ },
++ {
++ "if": {
++ "properties": {
++ "state": {
++ "const": "cancelled"
++ }
++ },
++ "required": [
++ "state"
++ ]
++ },
++ "then": {
++ "required": [
++ "cancel_reason"
++ ]
++ },
++ "else": {
++ "not": {
++ "required": [
++ "cancel_reason"
++ ]
++ }
++ }
++ }
++ ]
++ }
++ },
"zone.object-zone": {
"version": "1.0.0",
"owner": "zone",
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 8efaa4e2..0e3efbf5 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.88.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 0b907202..dd6ba61b 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.88.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex 12675282..c8118ace 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.88.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 c9e28364..78b75d02 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.88.0",
-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 cb824a77..4496f142 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.88.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 cae9ca4b..5a9ec185 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.88.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 b051ed13..6b58484e 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.88.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 e583ab79..e0026892 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.88.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 7f55238e..8d05cc23 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.88.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 c006bfe2..0935a7a2 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.88.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 663cd708..8585d1d3 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.88.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 b4f9f489..41012145 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.88.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 89f9c676..5a51279f 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.88.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 7580d0c2..f6e28ac2 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.88.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 ac635b1b..44623dcf 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.88.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 eae2ef0d..ebdec442 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.88.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 8d3feda6..2a304201 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRe.Stream API",
-- "version": "0.88.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 a25cf159..9c5930c7 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.88.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 a8d83a32..c9ad4cca 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.88.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 e8cf049a..912600c6 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.88.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 ab6284cf..91cb10d5 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.88.0",
-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 335cc336..178a6702 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.88.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 4c06db00..de6ad0f8 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.88.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 c51cb0ed..c0adac99 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.88.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 264c7853..f9280faa 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.88.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 f468ccfb..0ea2a92f 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.88.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.88.0): each operation lists the capabilities it performs; a token for 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.89.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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 235ee084..d07c2276 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -51,9 +51,9 @@ export interface LegacyIdentityMapEntry {
metadata?: {};
}
--/** [email protected] (owner: network) */
++/** [email protected] (owner: network) */
/**
-- * Claims of a short-lived RS256 client-credentials token issued by OpenVibe.Network to a service, app, mod or node principal. Replaces X-Internal-Key. App tokens (actor_type app) also carry project_id and env; receivers refuse env=sandbox unless they opted in.
++ * Claims of a short-lived RS256 client-credentials token issued by OpenVibe.Network to a service, app, mod, node or agent principal. Replaces X-Internal-Key. App tokens (actor_type app) also carry project_id and env; receivers refuse env=sandbox unless they opted in. Agent tokens (actor_type agent, sub agent:agt_<ULID>) are minted for the agent's host and also carry project_id, env and on_behalf_of (the owner), and optionally cap_confirm and act.
*/
export type ServiceTokenClaims = {
[k: string]: unknown | undefined;
@@@ -63,7 +63,7 @@ export type ServiceTokenClaims = {
*/
iss: string;
sub: string;
-- actor_type: "service" | "app" | "mod" | "node";
++ actor_type: "service" | "app" | "mod" | "node" | "agent";
/**
* @minItems 1
*/
@@@ -72,6 +72,10 @@ export type ServiceTokenClaims = {
* Granted capability ids; a trailing .* grants a family.
*/
cap: string[];
++ /**
++ * Agent tokens: capabilities the agent holds in confirm mode. Each use needs an approved network.confirmation-request@1, presented as OpenVibe-Confirmation: cnf_<ULID> and consumed by the owning service. Never also in cap, so a receiver that does not know agents refuses those actions.
++ */
++ cap_confirm?: string[];
/**
* Namespace constraints, e.g. live.* or mod.example.*. A developer app token carries its project_id (the whole project, as issued before namespaces were rows) and app.<project_id>.*: on Media, app.<project_id> and app.<project_id>.sandbox are the project's production and sandbox namespaces, with children below them.
*/
@@@ -80,17 +84,23 @@ export type ServiceTokenClaims = {
exp: number;
jti: string;
/**
-- * Developer project of an app principal (ADR-014). Services key tenancy by it. Absent on first-party service tokens.
++ * Developer project of an app or agent principal (ADR-014). Services key tenancy by it. Absent on first-party service tokens.
*/
project_id?: string;
/**
-- * Environment of an app principal. A receiver MUST refuse env=sandbox (401 token.sandbox_refused) unless it opted in to sandbox tokens. Absent on first-party service tokens, which are production.
++ * Environment of an app or agent principal. A receiver MUST refuse env=sandbox (401 token.sandbox_refused) unless it opted in to sandbox tokens. Absent on first-party service tokens, which are production.
*/
env?: "sandbox" | "production";
/**
-- * The person who authorized an app through the authorization-code flow. Absent on client_credentials tokens.
++ * The person who authorized an app through the authorization-code flow, or the owner an agent acts for (always present on agent tokens). Absent on client_credentials tokens.
*/
on_behalf_of?: string;
++ /**
++ * Agent tokens: the host that runs the agent and the token was minted for (RFC 8693 actor claim), a service or an app.
++ */
++ act?: {
++ sub: string;
++ };
[k: string]: unknown | undefined;
};
@@@ -52018,6 +52028,58 @@ export interface ConfirmationRequest {
created_at: string;
}
++/** [email protected] (owner: network) */
++/**
++ * network.confirmation.changed v1 (OpenVibe.Network; roadmap WS-Z2, docs/t2-projects-and-grants.md section 5; shape from OpenVibe.Network docs/contracts-proposal/network.confirmation.changed.v1.json). A confirmation request (network.confirmation-request@1) an agent raised for a sensitive capability was created, approved, denied, expired, cancelled or used. state is the state after the change, named as in network.confirmation-request@1: created leaves it pending (or approved when a standing rule covers it, with rule_id), used leaves it approved, and every other change names its state. A cancelled change says why in cancel_reason. Written to Network's outbox in the transaction that changes it, beside an audit row. Never carries the summary or details, which may hold message text: a reader that needs them asks Network with its own authority. Envelope: subject { type: confirmation, id: cnf_… }, visibility internal, on_behalf_of the owner, actor the owner who decided (or the system for an expiry, a cascade or a standing rule).
++ */
++export type NetworkConfirmationChangedPayload = {
++ [k: string]: unknown | undefined;
++} & {
++ confirmation_id: string;
++ /**
++ * The agent that asked.
++ */
++ agent_id: string;
++ /**
++ * The agent's developer project.
++ */
++ project_id: string;
++ capability: string;
++ /**
++ * The owning service of the capability: the only one that may consume or cancel the confirmation.
++ */
++ audience: string;
++ /**
++ * The state after the change. A use leaves it approved and sets used_at.
++ */
++ state: "pending" | "approved" | "denied" | "expired" | "cancelled";
++ change: "created" | "approved" | "denied" | "expired" | "cancelled" | "used";
++ /**
++ * On approved: the rule the owner chose (once creates none).
++ */
++ standing_rule?: "once" | "session" | "until" | "always";
++ /**
++ * On created through a standing rule, or on approved with a session, until or always rule: that rule.
++ */
++ rule_id?: number;
++ /**
++ * On cancelled: what took the authority away.
++ */
++ cancel_reason?:
++ | "service"
++ | "agent_paused"
++ | "agent_revoked"
++ | "grant_revoked"
++ | "grant_expired"
++ | "beyond_host"
++ | "member_removed"
++ | "account_erased"
++ | "app_revoked"
++ | "project_archived";
++ expires_at?: string;
++ changed_at?: string;
++};
++
/** [email protected] (owner: zone) */
/**
* zone.object-zone@1 (PLANNED; ADR-031 amendment 2026-10-02): one named object zone, as OpenVibe.Zone's control API returns it (GET /api/v1/zones/{zone_id}, the list under /api/v1/projects/{project_id}/zones, and the 201 of a create). Zone owns this record: its name, environment, state, access policy and per-zone quota setting. Media owns everything inside it: the objects (ordinary med_ objects in the namespace media_namespace), their keys, bytes and replicas, quota enforcement, usage counting and the one deletion path. Zone never lists keys or holds bytes. The S3 bucket name is `name`, resolved within the project and environment of the access key that signs the request, at https://s3.openvibe.media/<name>/<key>. `ovrn` is ovrn:zone:<project_id>:object-zone/<id>, with the same project_id and id as this record. The id is never reused, and neither is a name: within one project environment a name belongs to one zone for ever, deleted or not, so a signed request names exactly one zone id. Name and env are immutable. Every environment has exactly one `default` zone, whose media_namespace is the environment root. Zone creates it with the environment, and it cannot be deleted on its own. Its access is per-object, and every other zone's is private. JSON Schema cannot compare fields: contracts.zones.checkObjectZone also requires that ovrn names this project_id and id, and that media_namespace is the environment root of project_id and env, followed by .<id> unless the zone is the default. More fields may be added.
ddiff --git a/manifests/capabilities/network.confirmation.manage.json b/manifests/capabilities/network.confirmation.manage.json
nnew file mode 100644
iindex 00000000..0520e989
---- /dev/null
++++ b/manifests/capabilities/network.confirmation.manage.json
@@@ -0,0 +1,25 @@
++{
++ "id": "network.confirmation.manage",
++ "version": "1.0",
++ "owner": "network",
++ "status": "planned",
++ "visibility": "first-party",
++ "description": "As the owning service of a sensitive capability, ask an agent's owner to confirm one action and spend the approval: create a network.confirmation-request@1 (Network derives the owner from the agent, never from the body), read it, consume it once with the digest of the action, or cancel it. Each service sees only the confirmations it created. The owner reads, approves and denies in their inbox (/api/v1/confirmations) with their own session; no token holds that. Never grantable to apps, never delegated to agents (roadmap WS-Z2).",
++ "permissions": [
++ "confirmations:write",
++ "confirmations:read"
++ ],
++ "resourceConstraints": [
++ "owner"
++ ],
++ "quotaClass": "admin",
++ "events": [
++ "network.confirmation.changed"
++ ],
++ "implementedBy": [
++ "POST /internal/confirmations",
++ "GET /internal/confirmations/:id",
++ "POST /internal/confirmations/:id/consume",
++ "POST /internal/confirmations/:id/cancel"
++ ]
++}
ddiff --git a/manifests/services/network.json b/manifests/services/network.json
iindex b34921b4..8b1cb06c 100644
---- a/manifests/services/network.json
++++ b/manifests/services/network.json
@@@ -43,6 +43,7 @@
"network.coins.debit",
"network.coins.read",
"network.coins.transfer",
++ "network.confirmation.manage",
"network.follows.read",
"network.follows.write",
"network.integration.github.read",
@@@ -66,6 +67,7 @@
"network.app.created",
"network.app.revoked",
"network.block.changed",
++ "network.confirmation.changed",
"network.credential.revoked",
"network.credential.rotated",
"network.follow.created",
ddiff --git a/package-lock.json b/package-lock.json
iindex cf9e3e86..e1c47af5 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-contracts",
-- "version": "0.88.0",
++ "version": "0.89.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-contracts",
-- "version": "0.88.0",
++ "version": "0.89.0",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
ddiff --git a/package.json b/package.json
iindex 0488ec57..94b1ae08 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.88.0",
++ "version": "0.89.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
ddiff --git a/test/run.js b/test/run.js
iindex e79c428f..ec711c05 100644
---- a/test/run.js
++++ b/test/run.js
@@@ -595,6 +595,14 @@ ok(contracts.ids.newId('project').startsWith('prj_'), 'project ids use the prj p
const nodeClaims = { ...claims, sub: 'node:nod_01J0000000000000000000000Z', actor_type: 'node' };
ok(V(serviceAuth.signServiceToken(nodeClaims, kp.privateKey)).ok, 'node token accepted');
ok(V(serviceAuth.signServiceToken({ ...nodeClaims, sub: 'node:nod_42' }, kp.privateKey)).code === 'token.invalid_claims', 'a malformed node id is rejected');
++const agentId = contracts.ids.newId('agent');
++const agentClaims = { ...claims, sub: contracts.ids.principalSub({ type: 'agent', id: agentId }), actor_type: 'agent', cap: ['chat.presence.read'], cap_confirm: ['chat.message.send'], project_id: 'prj_01J0000000000000000000000Z', env: 'production', on_behalf_of: 'usr_01J0000000000000000000000Z', act: { sub: 'svc:actor' } };
++const agentTok = V(serviceAuth.signServiceToken(agentClaims, kp.privateKey));
++ok(agentTok.ok, 'agent token accepted');
++ok(!contracts.capabilities.check(agentTok.claims, 'chat.message.send').allowed, 'a cap_confirm capability is not granted by the token alone');
++ok(V(serviceAuth.signServiceToken((({ on_behalf_of, ...rest }) => rest)(agentClaims), kp.privateKey)).code === 'token.invalid_claims', 'an agent token needs on_behalf_of');
++ok(V(serviceAuth.signServiceToken({ ...agentClaims, actor_type: 'service' }, kp.privateKey)).code === 'token.invalid_claims', 'an agent sub needs actor_type agent');
++ok(V(serviceAuth.signServiceToken({ ...agentClaims, sub: 'agent:agt_42' }, kp.privateKey)).code === 'token.invalid_claims', 'a malformed agent id is rejected');
const run = (guard, headers) => new Promise((resolve) => {
const req = { headers, body: {} };