openvibers/openvibe.contracts Commits
v0.71.0: account export and deletion (roadmap WS-B task 7, ADR-033). Adds the events network.account.export_requested (export_id, subject, requested_at, deadline; each service pushes its part to POST /internal/account-exports/{id}/parts, network.account-export-part@1, capability network.account.export.contribute) and network.account.deleted (deletion_id, subject, aliases, requested_at, deleted_at; each service erases its rows once per deletion_id, keeps money rows pseudonymised, held media and tombstones under others' replies, and confirms at POST /internal/account-deletions/{id}/confirmations, network.account-deletion-confirmation@1, capability network.account.deletion.confirm), network.account-export@1, network.account-deletion@1 and network.account-data-receipt@1. Network's manifest lists the events and capabilities; Live, Chat, Community, Media and Games consume both.
Changes
@@@ -6,6 +6,19 @@ the notes were in the tag and commit messages (`git tag -n1`).## Unreleased++## 0.71.0 — 2026-09-27++++**Account export and deletion** (roadmap WS-B task 7, ADR-033).++- `network.account.export_requested@1` (internal): a person asked for a copy of their data. Each service that keeps data about people pushes its part to `POST /internal/account-exports/{export_id}/parts` (`network.account-export-part@1`: JSON files of the subject's own rows, at most 20 MB) with the new capability `network.account.export.contribute`, before the deadline (30 minutes).++- `network.account.deleted@1` (internal): the 30-day grace ended and Network erased what it owns. Each service erases its rows once per `deletion_id`, as follows:++ - authored content goes, except items with others' replies, which stay as authorless tombstones;++ - votes go, and counts are recomputed;++ - money rows stay, pseudonymised, and held media stays.++++ It then confirms at `POST /internal/account-deletions/{deletion_id}/confirmations` (`network.account-deletion-confirmation@1`, counts only) with `network.account.deletion.confirm`. `aliases` names subjects merged into the account earlier.++- `network.account-export@1` (the job the person sees: pending, ready, partial, expired or failed, with each service's state), `network.account-deletion@1` (scheduled, cancelled or deleted), and `network.account-data-receipt@1` (Network's answer to a part or confirmation).++- Live, Chat, Community, Media and Games list both events as consumed.++## 0.70.0 — 2026-09-27**Account merge, the revocation reason and module policy** (roadmap WS-B task 5, ADR-029).@@@ -2406,6 +2406,76 @@"compatibility": "backward","status": "active"},++ {++ "id": "network.account.export_requested",++ "version": "1.0.0",++ "owner": "network",++ "schema": "events/payloads/network.account.export_requested.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-033"++ },++ {++ "id": "network.account.deleted",++ "version": "1.0.0",++ "owner": "network",++ "schema": "events/payloads/network.account.deleted.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-033"++ },++ {++ "id": "network.account-export",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/account-export.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-033"++ },++ {++ "id": "network.account-export-part",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/account-export-part.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-033"++ },++ {++ "id": "network.account-deletion",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/account-deletion.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-033"++ },++ {++ "id": "network.account-deletion-confirmation",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/account-deletion-confirmation.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-033"++ },++ {++ "id": "network.account-data-receipt",++ "version": "1.0.0",++ "owner": "network",++ "schema": "network/account-data-receipt.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-033"++ },{"id": "network.subject.merged","version": "1.0.0",nnew file mode 100644@@@ -0,0 +1,44 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.account.deleted.v1.json",++ "title": "NetworkAccountDeletedPayload",++ "description": "network.account.deleted v1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033). An account's 30-day grace ended and Network erased what it owns; the subject is a tombstone and is never reused. Each service erases its own rows about the subject once per deletion_id: authored content is removed (an item with other people's replies beneath it stays as an authorless tombstone), likes, votes and reactions go with counts recomputed, money and ledger rows stay pseudonymised, and media under a retention hold stays. It then confirms: POST /internal/account-deletions/{deletion_id}/confirmations (network.account-deletion-confirmation@1, capability network.account.deletion.confirm).",++ "type": "object",++ "required": [++ "deletion_id",++ "subject",++ "requested_at",++ "deleted_at"++ ],++ "additionalProperties": false,++ "properties": {++ "deletion_id": {++ "type": "string",++ "pattern": "^del_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The deletion; a redelivery carries the same id."++ },++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The deleted account."++ },++ "aliases": {++ "type": "array",++ "maxItems": 50,++ "items": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "description": "Subjects merged into this account earlier (ADR-029); a service that still keeps rows under one erases them too."++ },++ "requested_at": {++ "type": "string",++ "format": "date-time",++ "description": "When the person scheduled the deletion."++ },++ "deleted_at": {++ "type": "string",++ "format": "date-time"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,35 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.account.export_requested.v1.json",++ "title": "NetworkAccountExportRequestedPayload",++ "description": "network.account.export_requested v1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033). A person asked for a copy of their data. Each service that keeps data about people pushes its part before the deadline: POST /internal/account-exports/{export_id}/parts (network.account-export-part@1, capability network.account.export.contribute), with only the subject's own rows. A redelivery carries the same export_id; a second part from the same service replaces the first.",++ "type": "object",++ "required": [++ "export_id",++ "subject",++ "requested_at",++ "deadline"++ ],++ "additionalProperties": false,++ "properties": {++ "export_id": {++ "type": "string",++ "pattern": "^exp_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The export job; a redelivery carries the same id."++ },++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person whose data is exported."++ },++ "requested_at": {++ "type": "string",++ "format": "date-time"++ },++ "deadline": {++ "type": "string",++ "format": "date-time",++ "description": "Network builds the archive when every expected service has answered, or at this time (30 minutes after the request); a later part is refused."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,32 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/account-data-receipt.v1.json",++ "title": "NetworkAccountDataReceipt",++ "description": "network.account-data-receipt@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): Network's answer to a service's export part (network.account.export.contribute) or deletion confirmation (network.account.deletion.confirm). replaced: this service had already sent one for the same job, and this one replaced it.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "service",++ "received_at",++ "replaced"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^(exp|del)_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The export or deletion."++ },++ "service": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,31}$"++ },++ "received_at": {++ "type": "string",++ "format": "date-time"++ },++ "replaced": {++ "type": "boolean"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,42 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/account-deletion-confirmation.v1.json",++ "title": "NetworkAccountDeletionConfirmation",++ "description": "network.account-deletion-confirmation@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service confirms it erased a deleted account's rows, POST /internal/account-deletions/{deletion_id}/confirmations with capability network.account.deletion.confirm (a token speaks only for its own service). Counts only: what it erased and what it keeps (money and ledger rows pseudonymised, media under a retention hold, tombstones that keep others' replies in place). A repeat confirmation replaces the first.",++ "type": "object",++ "required": [++ "subject",++ "completed_at",++ "erased"++ ],++ "additionalProperties": false,++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "Must be the deletion's subject."++ },++ "completed_at": {++ "type": "string",++ "format": "date-time"++ },++ "erased": {++ "type": "object",++ "maxProperties": 64,++ "additionalProperties": {++ "type": "integer",++ "minimum": 0++ },++ "description": "Rows erased, by kind (messages, follows, pastes, …)."++ },++ "retained": {++ "type": "object",++ "maxProperties": 64,++ "additionalProperties": {++ "type": "integer",++ "minimum": 0++ },++ "description": "Rows kept, by kind, each pseudonymised or held (ledger, held_media, tombstones, …)."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,44 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/account-deletion.v1.json",++ "title": "NetworkAccountDeletion",++ "description": "network.account-deletion@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): a person's scheduled account deletion. POST /api/v1/account/deletion (a fresh sign-in, auth_time within 10 minutes, and the username typed out) schedules it 30 days ahead; GET answers it; DELETE cancels it before delete_after.",++ "type": "object",++ "required": [++ "deletion_id",++ "status",++ "requested_at",++ "delete_after"++ ],++ "additionalProperties": false,++ "properties": {++ "deletion_id": {++ "type": "string",++ "pattern": "^del_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "status": {++ "enum": [++ "scheduled",++ "cancelled",++ "deleted"++ ]++ },++ "requested_at": {++ "type": "string",++ "format": "date-time"++ },++ "delete_after": {++ "type": "string",++ "format": "date-time",++ "description": "Nothing leaves Network before this time; the account works normally until then."++ },++ "cancelled_at": {++ "type": "string",++ "format": "date-time"++ },++ "deleted_at": {++ "type": "string",++ "format": "date-time"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,55 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/account-export-part.v1.json",++ "title": "NetworkAccountExportPart",++ "description": "network.account-export-part@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service's part of a person's data export, POST /internal/account-exports/{export_id}/parts with capability network.account.export.contribute (a token speaks only for its own service). Files are JSON documents of the subject's own rows (never another person's data), at most 20 MB in all; each lands in the archive as <service>/<name>. A second part from the same service replaces the first.",++ "type": "object",++ "required": [++ "subject",++ "files"++ ],++ "additionalProperties": false,++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "Must be the export's subject."++ },++ "files": {++ "type": "array",++ "maxItems": 64,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "name",++ "content"++ ],++ "properties": {++ "name": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_.-]{0,62}\\.json$",++ "description": "A flat file name ending in .json."++ },++ "content": {++ "description": "Any JSON value: the rows, as the service describes them."++ }++ }++ }++ },++ "truncated": {++ "type": "array",++ "maxItems": 64,++ "items": {++ "type": "string",++ "maxLength": 64++ },++ "description": "Files cut at the service's own row limit, named so the README says so."++ },++ "note": {++ "type": "string",++ "maxLength": 500,++ "description": "A line for the README, such as where media files are downloaded."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,86 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/account-export.v1.json",++ "title": "NetworkAccountExport",++ "description": "network.account-export@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): a person's data export job. POST /api/v1/account/export starts one (one open at a time, one a day) and GET /api/v1/account/export/{id} answers its state. When ready or partial, GET /api/v1/account/export/{id}/download serves the zip to the same person until expires_at.",++ "type": "object",++ "required": [++ "export_id",++ "status",++ "requested_at",++ "deadline",++ "services"++ ],++ "additionalProperties": false,++ "properties": {++ "export_id": {++ "type": "string",++ "pattern": "^exp_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "status": {++ "enum": [++ "pending",++ "ready",++ "partial",++ "expired",++ "failed"++ ],++ "description": "pending: waiting for the services' parts. ready: every expected service answered. partial: the deadline passed without some (services[].status missing). expired: the archive is gone (7 days). failed: the archive could not be built."++ },++ "requested_at": {++ "type": "string",++ "format": "date-time"++ },++ "deadline": {++ "type": "string",++ "format": "date-time"++ },++ "ready_at": {++ "type": "string",++ "format": "date-time",++ "description": "When the archive was built."++ },++ "expires_at": {++ "type": "string",++ "format": "date-time",++ "description": "The archive is deleted then (7 days after ready_at)."++ },++ "size_bytes": {++ "type": "integer",++ "minimum": 0++ },++ "services": {++ "type": "array",++ "maxItems": 64,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "service",++ "status"++ ],++ "properties": {++ "service": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,31}$"++ },++ "status": {++ "enum": [++ "received",++ "waiting",++ "missing"++ ]++ },++ "files": {++ "type": "integer",++ "minimum": 0++ },++ "bytes": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ }++ }++}nnew file mode 100644@@@ -0,0 +1,60 @@++# ADR-033: Exporting and deleting an account++++**Status:** Accepted 2026-09-27 (roadmap WS-B task 7). Implementation follows in OpenVibe.Network and each service that keeps data about a person.++++## Context and current evidence++++- A person's data is spread across services, each owning its rows by subject (ADR-001): Network (account, providers, sessions, OAuth grants, projects, OpenCoins, modules, follows, blocks, notifications), Live (profile, follows projection, channel points, streams, tokens, money until the Billing cutover), Chat (messages, DMs, rooms), Community (pastes, threads, posts, comments, votes), Media (objects), Games (characters).++- Network can already reach every service through one event (ADR-029's `network.subject.merged` is consumed by each owner), and every service already calls Network with a service token (principals, ADR-004).++- The revocation reason `account_deleted` exists in `network.user.token_valid_after` but nothing emits it.++- Money is not erasable: ledgers are kept for the retention the billing policy names (ADR-012), with the person pseudonymised.++++## Decision++++- **Export is a job the person starts, and each service contributes its own part.**++ - `POST /api/v1/account/export` (signed in) opens a job `exp_…`. One job may be open at a time, and one may be started per day.++ - Network emits `network.account.export_requested { export_id, subject, requested_at, deadline }` (visibility internal).++ - Each service that keeps data about people answers by pushing its part: `POST /internal/account-exports/:export_id/parts`, carrying `network.account-export-part@1` and the capability `network.account.export.contribute`. The token may only speak for its own service. A part is JSON files (the service's rows about the subject, never anyone else's data), at most 20 MB. A second part from the same service replaces the first.++ - Network adds its own part and builds the archive: `network/…`, `<service>/…`, and `README.txt` naming what each service sent. It is built when every expected service has answered, or at the deadline (30 minutes), whichever comes first. The expected services are the principals holding the contribute grant.++ - The job ends `ready`, or `partial` when a service did not answer, which is named in the README and in the status. `GET /api/v1/account/export/:id` answers `network.account-export@1`. `GET /api/v1/account/export/:id/download` serves the zip to the same person for 7 days; after that the archive is deleted.++- **Deletion is scheduled, has a grace period, and every service confirms it.**++ - `POST /api/v1/account/deletion` needs a fresh sign-in (`auth_time` within 10 minutes, as for a merge) and the username typed out. It schedules the deletion 30 days ahead (`del_…`, `network.account-deletion@1`).++ - Until then the account works normally and shows the date. `DELETE /api/v1/account/deletion` cancels it; nothing leaves Network before the date.++ - At the date, one Network transaction erases what Network owns:++ - linked providers, sessions and devices, OAuth tokens, codes and grants;++ - developer projects the person alone owns, with their credentials;++ - user modules, follows both ways, blocks both ways, notifications, preferences, push subscriptions, effects, username history and pending merge intents.++ - The OpenCoins balance is closed with one ledger entry (`reason: account_deleted`); ledger rows stay.++ - The user row becomes a tombstone: the username is released, and the email, password and profile are cleared. The subject is never reused, and it resolves as `deleted`, as do aliases merged into it.++ - Tokens are revoked strictly (`network.user.token_valid_after`, reason `account_deleted`). Network then emits `network.account.deleted { deletion_id, subject, requested_at, deleted_at }` (visibility internal).++ - **Each service erases its own rows about the subject in its own transaction**, once per `deletion_id`:++ - content the person authored is removed, except that an item with other people's replies beneath it stays as an authorless tombstone, so the replies keep their place;++ - likes, votes and reactions go, and cached counts are recomputed;++ - moderation records about the person's actions keep the pseudonymous subject;++ - money and ledger rows stay, pseudonymised (no name, email or free text of the person), for the billing policy's retention;++ - media under a retention hold (ADR-006) is kept and counted as retained.++++ It then confirms with `POST /internal/account-deletions/:deletion_id/confirmations`, carrying `network.account-deletion-confirmation@1` and the capability `network.account.deletion.confirm`, with counts of what it erased and retained.++ - Network records each confirmation. Staff see deletions still waiting on a service; Events redelivers until the service answers.++- **Nothing is exported or deleted for anyone but the signed-in person.** Staff do not start either flow. A person who lost access recovers the account first (ADR-029's staff merge, or provider recovery), then uses the same flow.++++## Alternatives considered++++- **Network calls each service's export endpoint:** rejected. It needs a Network → service credential and a route in every service; pushing parts reuses the service → Network tokens every service already has.++- **Immediate deletion:** rejected. A mistaken or coerced request cannot be undone, and services would erase before a person could change their mind.++- **Deleting ledger rows:** rejected by ADR-012; pseudonymised rows keep the books balanced.++++## Migration consequences++++Network gains the `account_exports`, `account_export_parts`, `account_deletions` and `account_deletion_confirmations` tables, a users `deleted_at`, and an hourly due-deletion check. Contracts gain two events, four schemas and two capabilities. Every service that stores data about people consumes both events. Until a service does, its export part is missing (the export says so) and its deletion confirmation is outstanding (staff see it).++++## Rollback++++Stop offering export and deletion (the account page hides both cards). Scheduled deletions can be cancelled by staff before their date. A deletion already carried out is not reversible, by design.++++## Acceptance tests++++- Export: a job gathers every expected service's part, the zip holds only the person's data, a missing service makes it `partial` and says which one, another person cannot download it, and it expires after 7 days.++- Deletion: it refuses without a fresh sign-in or the typed username, cancellation stops it, and nothing happens before the date.++- At the date, Network's rows are erased, the subject resolves as `deleted`, the old tokens are refused, and the event is emitted once.++- Each service's test erases its rows on `network.account.deleted`, keeps others' replies and money rows pseudonymised, confirms with counts, and applies a redelivery once.@@@ -36,3 +36,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each| [ADR-030](ADR-030-follow-graph.md) | Follow graph: Network owns follows by subject, Live keeps a projection; expand/migrate/contract with reconciliation | Accepted 2026-09-26; migration pending || [ADR-031](ADR-031-s3-compatible-surface.md) | S3-compatible subset for Media: bucket = project namespace, object CRUD + ListObjectsV2 + presign, per-project keys | Accepted 2026-09-26; implementation pending || [ADR-032](ADR-032-containers.md) | Containers: platform services stay systemd units (one adapter in ovhost, a reserved `runtime` field); tenant code isolation belongs to Stage C | Accepted 2026-09-26 |++| [ADR-033](ADR-033-account-export-and-deletion.md) | Account export and deletion: services push their export parts to Network; deletion after a 30-day grace, each service erases its rows and confirms; money rows kept pseudonymised | Accepted 2026-09-27; implementation in progress |nnew file mode 100644@@@ -0,0 +1 @@++{"id":"mrg_01JAB2C3D4E5F6G7H8J9K0MNPS","service":"live","received_at":"2026-09-27T21:01:00.000Z","replaced":false}nnew file mode 100644@@@ -0,0 +1 @@++{"id":"exp_01JAB2C3D4E5F6G7H8J9K0MNPS","service":"live","received_at":"2026-09-27T21:01:00.000Z","replaced":false}nnew file mode 100644@@@ -0,0 +1 @@++{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","completed_at":"2026-09-27T21:05:00.000Z","erased":{"messages":-1}}nnew file mode 100644@@@ -0,0 +1 @@++{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","completed_at":"2026-09-27T21:05:00.000Z","erased":{"messages":120,"dm_messages":8,"blocks":1},"retained":{"moderation_actions":2}}nnew file mode 100644@@@ -0,0 +1 @@++{"deletion_id":"del_01JAB2C3D4E5F6G7H8J9K0MNPT","status":"scheduled","requested_at":"2026-09-27T21:00:00.000Z"}nnew file mode 100644@@@ -0,0 +1 @@++{"deletion_id":"del_01JAB2C3D4E5F6G7H8J9K0MNPT","status":"scheduled","requested_at":"2026-09-27T21:00:00.000Z","delete_after":"2026-10-27T21:00:00.000Z"}nnew file mode 100644@@@ -0,0 +1 @@++{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","files":[{"name":"avatar.png","content":"iVBOR"}]}nnew file mode 100644@@@ -0,0 +1 @@++{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","files":[{"name":"../network/profile.json","content":{}}]}nnew file mode 100644@@@ -0,0 +1 @@++{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","files":[{"name":"follows.json","content":[{"channel":"xena","since":"2026-01-02T03:04:05.000Z"}]},{"name":"profile.json","content":{"username":"keep","bio":"hi"}}],"truncated":[],"note":"Stream recordings are Media's part."}nnew file mode 100644@@@ -0,0 +1 @@++{"export_id":"exp_01JAB2C3D4E5F6G7H8J9K0MNPS","status":"done","requested_at":"2026-09-27T21:00:00.000Z","deadline":"2026-09-27T21:30:00.000Z","services":[]}nnew file mode 100644@@@ -0,0 +1 @@++{"export_id":"exp_01JAB2C3D4E5F6G7H8J9K0MNPS","status":"partial","requested_at":"2026-09-27T21:00:00.000Z","deadline":"2026-09-27T21:30:00.000Z","ready_at":"2026-09-27T21:30:02.000Z","expires_at":"2026-10-04T21:30:02.000Z","size_bytes":48213,"services":[{"service":"network","status":"received","files":9,"bytes":30100},{"service":"live","status":"received","files":6,"bytes":18113},{"service":"games","status":"missing"}]}nnew file mode 100644@@@ -0,0 +1 @@++{"deletion_id":"del_01JAB2C3D4E5F6G7H8J9K0MNPT","subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","username":"keep","requested_at":"2026-08-28T21:00:00.000Z","deleted_at":"2026-09-27T21:00:00.000Z"}nnew file mode 100644@@@ -0,0 +1 @@++{"deletion_id":"del_01JAB2C3D4E5F6G7H8J9K0MNPT","subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","requested_at":"2026-08-28T21:00:00.000Z","deleted_at":"2026-09-27T21:00:00.000Z"}nnew file mode 100644@@@ -0,0 +1 @@++{"deletion_id":"del_01JAB2C3D4E5F6G7H8J9K0MNPT","subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","aliases":["usr_01JAB2C3D4E5F6G7H8J9K0MNPR"],"requested_at":"2026-08-28T21:00:00.000Z","deleted_at":"2026-09-27T21:00:00.000Z"}nnew file mode 100644@@@ -0,0 +1 @@++{"export_id":"exp_01JAB2C3D4E5F6G7H8J9K0MNPS","subject":"42","requested_at":"2026-09-27T21:00:00.000Z","deadline":"2026-09-27T21:30:00.000Z"}nnew file mode 100644@@@ -0,0 +1 @@++{"export_id":"exp_01JAB2C3D4E5F6G7H8J9K0MNPS","subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","requested_at":"2026-09-27T21:00:00.000Z"}nnew file mode 100644@@@ -0,0 +1 @@++{"export_id":"exp_01JAB2C3D4E5F6G7H8J9K0MNPS","subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","requested_at":"2026-09-27T21:00:00.000Z","deadline":"2026-09-27T21:30:00.000Z"}@@@ -25379,6 +25379,379 @@}}},++ "network.account.export_requested": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.account.export_requested.v1.json",++ "title": "NetworkAccountExportRequestedPayload",++ "description": "network.account.export_requested v1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033). A person asked for a copy of their data. Each service that keeps data about people pushes its part before the deadline: POST /internal/account-exports/{export_id}/parts (network.account-export-part@1, capability network.account.export.contribute), with only the subject's own rows. A redelivery carries the same export_id; a second part from the same service replaces the first.",++ "type": "object",++ "required": [++ "export_id",++ "subject",++ "requested_at",++ "deadline"++ ],++ "additionalProperties": false,++ "properties": {++ "export_id": {++ "type": "string",++ "pattern": "^exp_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The export job; a redelivery carries the same id."++ },++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person whose data is exported."++ },++ "requested_at": {++ "type": "string",++ "format": "date-time"++ },++ "deadline": {++ "type": "string",++ "format": "date-time",++ "description": "Network builds the archive when every expected service has answered, or at this time (30 minutes after the request); a later part is refused."++ }++ }++ }++ },++ "network.account.deleted": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/network.account.deleted.v1.json",++ "title": "NetworkAccountDeletedPayload",++ "description": "network.account.deleted v1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033). An account's 30-day grace ended and Network erased what it owns; the subject is a tombstone and is never reused. Each service erases its own rows about the subject once per deletion_id: authored content is removed (an item with other people's replies beneath it stays as an authorless tombstone), likes, votes and reactions go with counts recomputed, money and ledger rows stay pseudonymised, and media under a retention hold stays. It then confirms: POST /internal/account-deletions/{deletion_id}/confirmations (network.account-deletion-confirmation@1, capability network.account.deletion.confirm).",++ "type": "object",++ "required": [++ "deletion_id",++ "subject",++ "requested_at",++ "deleted_at"++ ],++ "additionalProperties": false,++ "properties": {++ "deletion_id": {++ "type": "string",++ "pattern": "^del_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The deletion; a redelivery carries the same id."++ },++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The deleted account."++ },++ "aliases": {++ "type": "array",++ "maxItems": 50,++ "items": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "description": "Subjects merged into this account earlier (ADR-029); a service that still keeps rows under one erases them too."++ },++ "requested_at": {++ "type": "string",++ "format": "date-time",++ "description": "When the person scheduled the deletion."++ },++ "deleted_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "network.account-export": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/account-export.v1.json",++ "title": "NetworkAccountExport",++ "description": "network.account-export@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): a person's data export job. POST /api/v1/account/export starts one (one open at a time, one a day) and GET /api/v1/account/export/{id} answers its state. When ready or partial, GET /api/v1/account/export/{id}/download serves the zip to the same person until expires_at.",++ "type": "object",++ "required": [++ "export_id",++ "status",++ "requested_at",++ "deadline",++ "services"++ ],++ "additionalProperties": false,++ "properties": {++ "export_id": {++ "type": "string",++ "pattern": "^exp_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "status": {++ "enum": [++ "pending",++ "ready",++ "partial",++ "expired",++ "failed"++ ],++ "description": "pending: waiting for the services' parts. ready: every expected service answered. partial: the deadline passed without some (services[].status missing). expired: the archive is gone (7 days). failed: the archive could not be built."++ },++ "requested_at": {++ "type": "string",++ "format": "date-time"++ },++ "deadline": {++ "type": "string",++ "format": "date-time"++ },++ "ready_at": {++ "type": "string",++ "format": "date-time",++ "description": "When the archive was built."++ },++ "expires_at": {++ "type": "string",++ "format": "date-time",++ "description": "The archive is deleted then (7 days after ready_at)."++ },++ "size_bytes": {++ "type": "integer",++ "minimum": 0++ },++ "services": {++ "type": "array",++ "maxItems": 64,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "service",++ "status"++ ],++ "properties": {++ "service": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,31}$"++ },++ "status": {++ "enum": [++ "received",++ "waiting",++ "missing"++ ]++ },++ "files": {++ "type": "integer",++ "minimum": 0++ },++ "bytes": {++ "type": "integer",++ "minimum": 0++ }++ }++ }++ }++ }++ }++ },++ "network.account-export-part": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/account-export-part.v1.json",++ "title": "NetworkAccountExportPart",++ "description": "network.account-export-part@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service's part of a person's data export, POST /internal/account-exports/{export_id}/parts with capability network.account.export.contribute (a token speaks only for its own service). Files are JSON documents of the subject's own rows (never another person's data), at most 20 MB in all; each lands in the archive as <service>/<name>. A second part from the same service replaces the first.",++ "type": "object",++ "required": [++ "subject",++ "files"++ ],++ "additionalProperties": false,++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "Must be the export's subject."++ },++ "files": {++ "type": "array",++ "maxItems": 64,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "name",++ "content"++ ],++ "properties": {++ "name": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_.-]{0,62}\\.json$",++ "description": "A flat file name ending in .json."++ },++ "content": {++ "description": "Any JSON value: the rows, as the service describes them."++ }++ }++ }++ },++ "truncated": {++ "type": "array",++ "maxItems": 64,++ "items": {++ "type": "string",++ "maxLength": 64++ },++ "description": "Files cut at the service's own row limit, named so the README says so."++ },++ "note": {++ "type": "string",++ "maxLength": 500,++ "description": "A line for the README, such as where media files are downloaded."++ }++ }++ }++ },++ "network.account-deletion": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/account-deletion.v1.json",++ "title": "NetworkAccountDeletion",++ "description": "network.account-deletion@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): a person's scheduled account deletion. POST /api/v1/account/deletion (a fresh sign-in, auth_time within 10 minutes, and the username typed out) schedules it 30 days ahead; GET answers it; DELETE cancels it before delete_after.",++ "type": "object",++ "required": [++ "deletion_id",++ "status",++ "requested_at",++ "delete_after"++ ],++ "additionalProperties": false,++ "properties": {++ "deletion_id": {++ "type": "string",++ "pattern": "^del_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "status": {++ "enum": [++ "scheduled",++ "cancelled",++ "deleted"++ ]++ },++ "requested_at": {++ "type": "string",++ "format": "date-time"++ },++ "delete_after": {++ "type": "string",++ "format": "date-time",++ "description": "Nothing leaves Network before this time; the account works normally until then."++ },++ "cancelled_at": {++ "type": "string",++ "format": "date-time"++ },++ "deleted_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "network.account-deletion-confirmation": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/account-deletion-confirmation.v1.json",++ "title": "NetworkAccountDeletionConfirmation",++ "description": "network.account-deletion-confirmation@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service confirms it erased a deleted account's rows, POST /internal/account-deletions/{deletion_id}/confirmations with capability network.account.deletion.confirm (a token speaks only for its own service). Counts only: what it erased and what it keeps (money and ledger rows pseudonymised, media under a retention hold, tombstones that keep others' replies in place). A repeat confirmation replaces the first.",++ "type": "object",++ "required": [++ "subject",++ "completed_at",++ "erased"++ ],++ "additionalProperties": false,++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "Must be the deletion's subject."++ },++ "completed_at": {++ "type": "string",++ "format": "date-time"++ },++ "erased": {++ "type": "object",++ "maxProperties": 64,++ "additionalProperties": {++ "type": "integer",++ "minimum": 0++ },++ "description": "Rows erased, by kind (messages, follows, pastes, …)."++ },++ "retained": {++ "type": "object",++ "maxProperties": 64,++ "additionalProperties": {++ "type": "integer",++ "minimum": 0++ },++ "description": "Rows kept, by kind, each pseudonymised or held (ledger, held_media, tombstones, …)."++ }++ }++ }++ },++ "network.account-data-receipt": {++ "version": "1.0.0",++ "owner": "network",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/network/account-data-receipt.v1.json",++ "title": "NetworkAccountDataReceipt",++ "description": "network.account-data-receipt@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): Network's answer to a service's export part (network.account.export.contribute) or deletion confirmation (network.account.deletion.confirm). replaced: this service had already sent one for the same job, and this one replaced it.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "service",++ "received_at",++ "replaced"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^(exp|del)_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The export or deletion."++ },++ "service": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,31}$"++ },++ "received_at": {++ "type": "string",++ "format": "date-time"++ },++ "replaced": {++ "type": "boolean"++ }++ }++ }++ },"network.subject.merged": {"version": "1.0.0","owner": "network",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.AI API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Billing API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Blog API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Chat API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Codes API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Community API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Coupons API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Deals API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Events API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Games API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Host API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for 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"@@@ -96,8 +96,8 @@"service": "network","name": "OpenVibe.Network","origin": "https://openvibe.network",-- "operations": 18,-- "capabilities": 15++ "operations": 20,++ "capabilities": 17},{"service": "news",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Live API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Media API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Network API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for 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"@@@ -304,6 +304,138 @@}}},++ "/internal/account-deletions/{deletion_id}/confirmations": {++ "post": {++ "operationId": "post_internal_account_deletions_deletion_id_confirmations",++ "summary": "Confirm that a deleted account's rows are erased at a service (ADR-033), with counts of what was erased and what is kept (pseudonymised money rows, held media, tombstones).",++ "description": "**network.account.deletion.confirm** (internal): Confirm that a deleted account's rows are erased at a service (ADR-033), with counts of what was erased and what is kept (pseudonymised money rows, held media, tombstones). A token may only speak for its own service.",++ "tags": [++ "network.account"++ ],++ "x-openvibe-capabilities": [++ "network.account.deletion.confirm"++ ],++ "x-openvibe-visibility": [++ "internal"++ ],++ "x-openvibe-quota-class": "account-data",++ "security": [++ {++ "openvibeToken": [++ "network.account.deletion.confirm"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.account-deletion-confirmation.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "deletion_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.account-data-receipt.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/internal/account-exports/{export_id}/parts": {++ "post": {++ "operationId": "post_internal_account_exports_export_id_parts",++ "summary": "Push a service's part of a person's data export (ADR-033): the subject's own rows as JSON files, before the export's deadline.",++ "description": "**network.account.export.contribute** (internal): Push a service's part of a person's data export (ADR-033): the subject's own rows as JSON files, before the export's deadline. A token may only speak for its own service; a second part replaces the first.",++ "tags": [++ "network.account"++ ],++ "x-openvibe-capabilities": [++ "network.account.export.contribute"++ ],++ "x-openvibe-visibility": [++ "internal"++ ],++ "x-openvibe-quota-class": "account-data",++ "security": [++ {++ "openvibeToken": [++ "network.account.export.contribute"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.account-export-part.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "export_id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/network.account-data-receipt.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },"/internal/blocks?subject=usr_…": {"get": {"operationId": "get_internal_blocks_subject_usr",@@@ -1438,6 +1570,129 @@}}},++ "network.account-data-receipt.v1": {++ "title": "NetworkAccountDataReceipt",++ "description": "network.account-data-receipt@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): Network's answer to a service's export part (network.account.export.contribute) or deletion confirmation (network.account.deletion.confirm). replaced: this service had already sent one for the same job, and this one replaced it.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "service",++ "received_at",++ "replaced"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^(exp|del)_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The export or deletion."++ },++ "service": {++ "type": "string",++ "pattern": "^[a-z][a-z0-9-]{1,31}$"++ },++ "received_at": {++ "type": "string",++ "format": "date-time"++ },++ "replaced": {++ "type": "boolean"++ }++ }++ },++ "network.account-deletion-confirmation.v1": {++ "title": "NetworkAccountDeletionConfirmation",++ "description": "network.account-deletion-confirmation@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service confirms it erased a deleted account's rows, POST /internal/account-deletions/{deletion_id}/confirmations with capability network.account.deletion.confirm (a token speaks only for its own service). Counts only: what it erased and what it keeps (money and ledger rows pseudonymised, media under a retention hold, tombstones that keep others' replies in place). A repeat confirmation replaces the first.",++ "type": "object",++ "required": [++ "subject",++ "completed_at",++ "erased"++ ],++ "additionalProperties": false,++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "Must be the deletion's subject."++ },++ "completed_at": {++ "type": "string",++ "format": "date-time"++ },++ "erased": {++ "type": "object",++ "maxProperties": 64,++ "additionalProperties": {++ "type": "integer",++ "minimum": 0++ },++ "description": "Rows erased, by kind (messages, follows, pastes, …)."++ },++ "retained": {++ "type": "object",++ "maxProperties": 64,++ "additionalProperties": {++ "type": "integer",++ "minimum": 0++ },++ "description": "Rows kept, by kind, each pseudonymised or held (ledger, held_media, tombstones, …)."++ }++ }++ },++ "network.account-export-part.v1": {++ "title": "NetworkAccountExportPart",++ "description": "network.account-export-part@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service's part of a person's data export, POST /internal/account-exports/{export_id}/parts with capability network.account.export.contribute (a token speaks only for its own service). Files are JSON documents of the subject's own rows (never another person's data), at most 20 MB in all; each lands in the archive as <service>/<name>. A second part from the same service replaces the first.",++ "type": "object",++ "required": [++ "subject",++ "files"++ ],++ "additionalProperties": false,++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "Must be the export's subject."++ },++ "files": {++ "type": "array",++ "maxItems": 64,++ "items": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "name",++ "content"++ ],++ "properties": {++ "name": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9_.-]{0,62}\\.json$",++ "description": "A flat file name ending in .json."++ },++ "content": {++ "description": "Any JSON value: the rows, as the service describes them."++ }++ }++ }++ },++ "truncated": {++ "type": "array",++ "maxItems": 64,++ "items": {++ "type": "string",++ "maxLength": 64++ },++ "description": "Files cut at the service's own row limit, named so the README says so."++ },++ "note": {++ "type": "string",++ "maxLength": 500,++ "description": "A line for the README, such as where media files are downloaded."++ }++ }++ },"network.blocks-result.v1": {"title": "NetworkBlocksResult","description": "network.blocks-result@1: the answer of network.blocks.read on OpenVibe.Network. GET /internal/blocks?subject=usr_… (a service token holding network.blocks.read; the shared internal key is refused) → { subject, blocks, blocked_by }: the people the subject has blocked and the people who have blocked the subject, as usr_ subjects in subject order, active blocks only (a person may hold at most 5000). Answered with Cache-Control: no-store. A subject that is not a usr_ id is refused with 400 blocks.bad_subject (problem+json).",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.News API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenRe.Stream API",-- "version": "0.70.0",-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Reviews API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Search API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Sources API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tips API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tools API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Trade API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.VIP API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Wiki API",-- "version": "0.70.0",-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for 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"@@@ -28636,6 +28636,174 @@ export interface NetworkPrincipalGrantChangedPayload {actor_subject?: string | null;}++/** [email protected] (owner: network) */++/**++ * network.account.export_requested v1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033). A person asked for a copy of their data. Each service that keeps data about people pushes its part before the deadline: POST /internal/account-exports/{export_id}/parts (network.account-export-part@1, capability network.account.export.contribute), with only the subject's own rows. A redelivery carries the same export_id; a second part from the same service replaces the first.++ */++export interface NetworkAccountExportRequestedPayload {++ /**++ * The export job; a redelivery carries the same id.++ */++ export_id: string;++ /**++ * The person whose data is exported.++ */++ subject: string;++ requested_at: string;++ /**++ * Network builds the archive when every expected service has answered, or at this time (30 minutes after the request); a later part is refused.++ */++ deadline: string;++}++++/** [email protected] (owner: network) */++/**++ * network.account.deleted v1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033). An account's 30-day grace ended and Network erased what it owns; the subject is a tombstone and is never reused. Each service erases its own rows about the subject once per deletion_id: authored content is removed (an item with other people's replies beneath it stays as an authorless tombstone), likes, votes and reactions go with counts recomputed, money and ledger rows stay pseudonymised, and media under a retention hold stays. It then confirms: POST /internal/account-deletions/{deletion_id}/confirmations (network.account-deletion-confirmation@1, capability network.account.deletion.confirm).++ */++export interface NetworkAccountDeletedPayload {++ /**++ * The deletion; a redelivery carries the same id.++ */++ deletion_id: string;++ /**++ * The deleted account.++ */++ subject: string;++ /**++ * Subjects merged into this account earlier (ADR-029); a service that still keeps rows under one erases them too.++ *++ * @maxItems 50++ */++ aliases?: string[];++ /**++ * When the person scheduled the deletion.++ */++ requested_at: string;++ deleted_at: string;++}++++/** [email protected] (owner: network) */++/**++ * network.account-export@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): a person's data export job. POST /api/v1/account/export starts one (one open at a time, one a day) and GET /api/v1/account/export/{id} answers its state. When ready or partial, GET /api/v1/account/export/{id}/download serves the zip to the same person until expires_at.++ */++export interface NetworkAccountExport {++ export_id: string;++ /**++ * pending: waiting for the services' parts. ready: every expected service answered. partial: the deadline passed without some (services[].status missing). expired: the archive is gone (7 days). failed: the archive could not be built.++ */++ status: "pending" | "ready" | "partial" | "expired" | "failed";++ requested_at: string;++ deadline: string;++ /**++ * When the archive was built.++ */++ ready_at?: string;++ /**++ * The archive is deleted then (7 days after ready_at).++ */++ expires_at?: string;++ size_bytes?: number;++ /**++ * @maxItems 64++ */++ services: {++ service: string;++ status: "received" | "waiting" | "missing";++ files?: number;++ bytes?: number;++ }[];++}++++/** [email protected] (owner: network) */++/**++ * network.account-export-part@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service's part of a person's data export, POST /internal/account-exports/{export_id}/parts with capability network.account.export.contribute (a token speaks only for its own service). Files are JSON documents of the subject's own rows (never another person's data), at most 20 MB in all; each lands in the archive as <service>/<name>. A second part from the same service replaces the first.++ */++export interface NetworkAccountExportPart {++ /**++ * Must be the export's subject.++ */++ subject: string;++ /**++ * @maxItems 64++ */++ files: {++ /**++ * A flat file name ending in .json.++ */++ name: string;++ /**++ * Any JSON value: the rows, as the service describes them.++ */++ content: {++ [k: string]: unknown | undefined;++ };++ }[];++ /**++ * Files cut at the service's own row limit, named so the README says so.++ *++ * @maxItems 64++ */++ truncated?: string[];++ /**++ * A line for the README, such as where media files are downloaded.++ */++ note?: string;++}++++/** [email protected] (owner: network) */++/**++ * network.account-deletion@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): a person's scheduled account deletion. POST /api/v1/account/deletion (a fresh sign-in, auth_time within 10 minutes, and the username typed out) schedules it 30 days ahead; GET answers it; DELETE cancels it before delete_after.++ */++export interface NetworkAccountDeletion {++ deletion_id: string;++ status: "scheduled" | "cancelled" | "deleted";++ requested_at: string;++ /**++ * Nothing leaves Network before this time; the account works normally until then.++ */++ delete_after: string;++ cancelled_at?: string;++ deleted_at?: string;++}++++/** [email protected] (owner: network) */++/**++ * network.account-deletion-confirmation@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service confirms it erased a deleted account's rows, POST /internal/account-deletions/{deletion_id}/confirmations with capability network.account.deletion.confirm (a token speaks only for its own service). Counts only: what it erased and what it keeps (money and ledger rows pseudonymised, media under a retention hold, tombstones that keep others' replies in place). A repeat confirmation replaces the first.++ */++export interface NetworkAccountDeletionConfirmation {++ /**++ * Must be the deletion's subject.++ */++ subject: string;++ completed_at: string;++ /**++ * Rows erased, by kind (messages, follows, pastes, …).++ */++ erased: {++ [k: string]: number | undefined;++ };++ /**++ * Rows kept, by kind, each pseudonymised or held (ledger, held_media, tombstones, …).++ */++ retained?: {++ [k: string]: number | undefined;++ };++}++++/** [email protected] (owner: network) */++/**++ * network.account-data-receipt@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): Network's answer to a service's export part (network.account.export.contribute) or deletion confirmation (network.account.deletion.confirm). replaced: this service had already sent one for the same job, and this one replaced it.++ */++export interface NetworkAccountDataReceipt {++ /**++ * The export or deletion.++ */++ id: string;++ service: string;++ received_at: string;++ replaced: boolean;++}++/** [email protected] (owner: network) *//*** network.subject.merged v1 (OpenVibe.Network; roadmap WS-B task 5, ADR-029). Two accounts became one: `from`, the folded-in subject, is now an alias of `into`, the survivor, which keeps its subject, username, profile and settings. Network has already moved what it owns (linked providers, sessions, OAuth grants, developer projects, OpenCoins and user modules) in the transaction that writes this event to its outbox. Every service that stores subjects repoints its own rows from `from` to `into` in its own transaction and keeps no copy under `from`; a row it cannot move (a unique conflict, such as both accounts following the same channel) keeps the survivor's row and drops the other, as a counted, logged outcome. Until a service has applied it, its rows stay under `from`, which still resolves to `into` through Network's aliases. Apply once per merge_id. Envelope: subject { type: user, id: <into> }, visibility internal, actor the person who merged (or the staff member, for an account-recovery merge).nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "network.account.deletion.confirm",++ "version": "1.0",++ "owner": "network",++ "status": "active",++ "visibility": "internal",++ "description": "Confirm that a deleted account's rows are erased at a service (ADR-033), with counts of what was erased and what is kept (pseudonymised money rows, held media, tombstones). A token may only speak for its own service.",++ "permissions": [++ "account:delete"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "account-data",++ "events": [],++ "implementedBy": [++ "POST /internal/account-deletions/:deletion_id/confirmations"++ ],++ "inputSchema": "network.account-deletion-confirmation@1",++ "outputSchema": "network.account-data-receipt@1"++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "network.account.export.contribute",++ "version": "1.0",++ "owner": "network",++ "status": "active",++ "visibility": "internal",++ "description": "Push a service's part of a person's data export (ADR-033): the subject's own rows as JSON files, before the export's deadline. A token may only speak for its own service; a second part replaces the first.",++ "permissions": [++ "account:export"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "account-data",++ "events": [],++ "implementedBy": [++ "POST /internal/account-exports/:export_id/parts"++ ],++ "inputSchema": "network.account-export-part@1",++ "outputSchema": "network.account-data-receipt@1"++}@@@ -25,6 +25,8 @@"chat.room.message.deleted"],"eventsConsumed": [++ "network.account.deleted",++ "network.account.export_requested","network.block.changed","network.user.token_valid_after"],@@@ -38,6 +38,8 @@"community.thread.created"],"eventsConsumed": [++ "network.account.deleted",++ "network.account.export_requested","network.block.changed","network.user.token_valid_after"],@@@ -47,6 +47,8 @@"games.moderation.action"],"eventsConsumed": [++ "network.account.deleted",++ "network.account.export_requested","network.user.token_valid_after"],"namespacesOwned": [@@@ -51,6 +51,8 @@"media.clip.*","media.storage.*","media.vod.*",++ "network.account.deleted",++ "network.account.export_requested","network.user.token_valid_after","network.user.updated","openre.session.*"@@@ -59,6 +59,8 @@"media.vod.ready"],"eventsConsumed": [++ "network.account.deleted",++ "network.account.export_requested","network.user.token_valid_after"],"namespacesOwned": [@@@ -34,6 +34,8 @@"capabilities": ["identity.subject.resolve","mods.grant.manage",++ "network.account.deletion.confirm",++ "network.account.export.contribute","network.analytics.creator.read","network.blocks.read","network.coins.credit",@@@ -60,6 +62,8 @@"network.follow.created","network.follow.deleted","network.subject.merged",++ "network.account.export_requested",++ "network.account.deleted","network.grant.changed","network.module.updated","network.notification.created",@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.70.0",++ "version": "0.71.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",
@@ -6,6 +6,19 @@ the notes were in the tag and commit messages (`git tag -n1`).
@@ -6,6 +6,19 @@ the notes were in the tag and commit messages (`git tag -n1`).
## Unreleased
## Unreleased
+## 0.71.0 — 2026-09-27
+
+**Account export and deletion** (roadmap WS-B task 7, ADR-033).
+- `network.account.export_requested@1` (internal): a person asked for a copy of their data. Each service that keeps data about people pushes its part to `POST /internal/account-exports/{export_id}/parts` (`network.account-export-part@1`: JSON files of the subject's own rows, at most 20 MB) with the new capability `network.account.export.contribute`, before the deadline (30 minutes).
+- `network.account.deleted@1` (internal): the 30-day grace ended and Network erased what it owns. Each service erases its rows once per `deletion_id`, as follows:
+ - authored content goes, except items with others' replies, which stay as authorless tombstones;
+ - votes go, and counts are recomputed;
+ - money rows stay, pseudonymised, and held media stays.
+
+ It then confirms at `POST /internal/account-deletions/{deletion_id}/confirmations` (`network.account-deletion-confirmation@1`, counts only) with `network.account.deletion.confirm`. `aliases` names subjects merged into the account earlier.
+- `network.account-export@1` (the job the person sees: pending, ready, partial, expired or failed, with each service's state), `network.account-deletion@1` (scheduled, cancelled or deleted), and `network.account-data-receipt@1` (Network's answer to a part or confirmation).
+- Live, Chat, Community, Media and Games list both events as consumed.
+
## 0.70.0 — 2026-09-27
## 0.70.0 — 2026-09-27
**Account merge, the revocation reason and module policy** (roadmap WS-B task 5, ADR-029).
**Account merge, the revocation reason and module policy** (roadmap WS-B task 5, ADR-029).
@@ -2406,6 +2406,76 @@
@@ -2406,6 +2406,76 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"status": "active"
},
},
+ {
+ "id": "network.account.export_requested",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "events/payloads/network.account.export_requested.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-033"
+ },
+ {
+ "id": "network.account.deleted",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "events/payloads/network.account.deleted.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-033"
+ },
+ {
+ "id": "network.account-export",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/account-export.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-033"
+ },
+ {
+ "id": "network.account-export-part",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/account-export-part.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-033"
+ },
+ {
+ "id": "network.account-deletion",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/account-deletion.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-033"
+ },
+ {
+ "id": "network.account-deletion-confirmation",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/account-deletion-confirmation.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-033"
+ },
+ {
+ "id": "network.account-data-receipt",
+ "version": "1.0.0",
+ "owner": "network",
+ "schema": "network/account-data-receipt.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-033"
+ },
{
{
"id": "network.subject.merged",
"id": "network.subject.merged",
"version": "1.0.0",
"version": "1.0.0",
new file mode 100644
new file mode 100644
@@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.account.deleted.v1.json",
+ "title": "NetworkAccountDeletedPayload",
+ "description": "network.account.deleted v1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033). An account's 30-day grace ended and Network erased what it owns; the subject is a tombstone and is never reused. Each service erases its own rows about the subject once per deletion_id: authored content is removed (an item with other people's replies beneath it stays as an authorless tombstone), likes, votes and reactions go with counts recomputed, money and ledger rows stay pseudonymised, and media under a retention hold stays. It then confirms: POST /internal/account-deletions/{deletion_id}/confirmations (network.account-deletion-confirmation@1, capability network.account.deletion.confirm).",
+ "type": "object",
+ "required": [
+ "deletion_id",
+ "subject",
+ "requested_at",
+ "deleted_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "deletion_id": {
+ "type": "string",
+ "pattern": "^del_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The deletion; a redelivery carries the same id."
+ },
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The deleted account."
+ },
+ "aliases": {
+ "type": "array",
+ "maxItems": 50,
+ "items": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "description": "Subjects merged into this account earlier (ADR-029); a service that still keeps rows under one erases them too."
+ },
+ "requested_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the person scheduled the deletion."
+ },
+ "deleted_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.account.export_requested.v1.json",
+ "title": "NetworkAccountExportRequestedPayload",
+ "description": "network.account.export_requested v1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033). A person asked for a copy of their data. Each service that keeps data about people pushes its part before the deadline: POST /internal/account-exports/{export_id}/parts (network.account-export-part@1, capability network.account.export.contribute), with only the subject's own rows. A redelivery carries the same export_id; a second part from the same service replaces the first.",
+ "type": "object",
+ "required": [
+ "export_id",
+ "subject",
+ "requested_at",
+ "deadline"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "export_id": {
+ "type": "string",
+ "pattern": "^exp_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The export job; a redelivery carries the same id."
+ },
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person whose data is exported."
+ },
+ "requested_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "deadline": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Network builds the archive when every expected service has answered, or at this time (30 minutes after the request); a later part is refused."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/account-data-receipt.v1.json",
+ "title": "NetworkAccountDataReceipt",
+ "description": "network.account-data-receipt@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): Network's answer to a service's export part (network.account.export.contribute) or deletion confirmation (network.account.deletion.confirm). replaced: this service had already sent one for the same job, and this one replaced it.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "service",
+ "received_at",
+ "replaced"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^(exp|del)_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The export or deletion."
+ },
+ "service": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,31}$"
+ },
+ "received_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "replaced": {
+ "type": "boolean"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/account-deletion-confirmation.v1.json",
+ "title": "NetworkAccountDeletionConfirmation",
+ "description": "network.account-deletion-confirmation@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service confirms it erased a deleted account's rows, POST /internal/account-deletions/{deletion_id}/confirmations with capability network.account.deletion.confirm (a token speaks only for its own service). Counts only: what it erased and what it keeps (money and ledger rows pseudonymised, media under a retention hold, tombstones that keep others' replies in place). A repeat confirmation replaces the first.",
+ "type": "object",
+ "required": [
+ "subject",
+ "completed_at",
+ "erased"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "Must be the deletion's subject."
+ },
+ "completed_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "erased": {
+ "type": "object",
+ "maxProperties": 64,
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Rows erased, by kind (messages, follows, pastes, …)."
+ },
+ "retained": {
+ "type": "object",
+ "maxProperties": 64,
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Rows kept, by kind, each pseudonymised or held (ledger, held_media, tombstones, …)."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/account-deletion.v1.json",
+ "title": "NetworkAccountDeletion",
+ "description": "network.account-deletion@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): a person's scheduled account deletion. POST /api/v1/account/deletion (a fresh sign-in, auth_time within 10 minutes, and the username typed out) schedules it 30 days ahead; GET answers it; DELETE cancels it before delete_after.",
+ "type": "object",
+ "required": [
+ "deletion_id",
+ "status",
+ "requested_at",
+ "delete_after"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "deletion_id": {
+ "type": "string",
+ "pattern": "^del_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "status": {
+ "enum": [
+ "scheduled",
+ "cancelled",
+ "deleted"
+ ]
+ },
+ "requested_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "delete_after": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Nothing leaves Network before this time; the account works normally until then."
+ },
+ "cancelled_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "deleted_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/account-export-part.v1.json",
+ "title": "NetworkAccountExportPart",
+ "description": "network.account-export-part@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service's part of a person's data export, POST /internal/account-exports/{export_id}/parts with capability network.account.export.contribute (a token speaks only for its own service). Files are JSON documents of the subject's own rows (never another person's data), at most 20 MB in all; each lands in the archive as <service>/<name>. A second part from the same service replaces the first.",
+ "type": "object",
+ "required": [
+ "subject",
+ "files"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "Must be the export's subject."
+ },
+ "files": {
+ "type": "array",
+ "maxItems": 64,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "name",
+ "content"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_.-]{0,62}\\.json$",
+ "description": "A flat file name ending in .json."
+ },
+ "content": {
+ "description": "Any JSON value: the rows, as the service describes them."
+ }
+ }
+ }
+ },
+ "truncated": {
+ "type": "array",
+ "maxItems": 64,
+ "items": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "description": "Files cut at the service's own row limit, named so the README says so."
+ },
+ "note": {
+ "type": "string",
+ "maxLength": 500,
+ "description": "A line for the README, such as where media files are downloaded."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/account-export.v1.json",
+ "title": "NetworkAccountExport",
+ "description": "network.account-export@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): a person's data export job. POST /api/v1/account/export starts one (one open at a time, one a day) and GET /api/v1/account/export/{id} answers its state. When ready or partial, GET /api/v1/account/export/{id}/download serves the zip to the same person until expires_at.",
+ "type": "object",
+ "required": [
+ "export_id",
+ "status",
+ "requested_at",
+ "deadline",
+ "services"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "export_id": {
+ "type": "string",
+ "pattern": "^exp_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "status": {
+ "enum": [
+ "pending",
+ "ready",
+ "partial",
+ "expired",
+ "failed"
+ ],
+ "description": "pending: waiting for the services' parts. ready: every expected service answered. partial: the deadline passed without some (services[].status missing). expired: the archive is gone (7 days). failed: the archive could not be built."
+ },
+ "requested_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "deadline": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "ready_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the archive was built."
+ },
+ "expires_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "The archive is deleted then (7 days after ready_at)."
+ },
+ "size_bytes": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "services": {
+ "type": "array",
+ "maxItems": 64,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "service",
+ "status"
+ ],
+ "properties": {
+ "service": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,31}$"
+ },
+ "status": {
+ "enum": [
+ "received",
+ "waiting",
+ "missing"
+ ]
+ },
+ "files": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "bytes": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
+# ADR-033: Exporting and deleting an account
+
+**Status:** Accepted 2026-09-27 (roadmap WS-B task 7). Implementation follows in OpenVibe.Network and each service that keeps data about a person.
+
+## Context and current evidence
+
+- A person's data is spread across services, each owning its rows by subject (ADR-001): Network (account, providers, sessions, OAuth grants, projects, OpenCoins, modules, follows, blocks, notifications), Live (profile, follows projection, channel points, streams, tokens, money until the Billing cutover), Chat (messages, DMs, rooms), Community (pastes, threads, posts, comments, votes), Media (objects), Games (characters).
+- Network can already reach every service through one event (ADR-029's `network.subject.merged` is consumed by each owner), and every service already calls Network with a service token (principals, ADR-004).
+- The revocation reason `account_deleted` exists in `network.user.token_valid_after` but nothing emits it.
+- Money is not erasable: ledgers are kept for the retention the billing policy names (ADR-012), with the person pseudonymised.
+
+## Decision
+
+- **Export is a job the person starts, and each service contributes its own part.**
+ - `POST /api/v1/account/export` (signed in) opens a job `exp_…`. One job may be open at a time, and one may be started per day.
+ - Network emits `network.account.export_requested { export_id, subject, requested_at, deadline }` (visibility internal).
+ - Each service that keeps data about people answers by pushing its part: `POST /internal/account-exports/:export_id/parts`, carrying `network.account-export-part@1` and the capability `network.account.export.contribute`. The token may only speak for its own service. A part is JSON files (the service's rows about the subject, never anyone else's data), at most 20 MB. A second part from the same service replaces the first.
+ - Network adds its own part and builds the archive: `network/…`, `<service>/…`, and `README.txt` naming what each service sent. It is built when every expected service has answered, or at the deadline (30 minutes), whichever comes first. The expected services are the principals holding the contribute grant.
+ - The job ends `ready`, or `partial` when a service did not answer, which is named in the README and in the status. `GET /api/v1/account/export/:id` answers `network.account-export@1`. `GET /api/v1/account/export/:id/download` serves the zip to the same person for 7 days; after that the archive is deleted.
+- **Deletion is scheduled, has a grace period, and every service confirms it.**
+ - `POST /api/v1/account/deletion` needs a fresh sign-in (`auth_time` within 10 minutes, as for a merge) and the username typed out. It schedules the deletion 30 days ahead (`del_…`, `network.account-deletion@1`).
+ - Until then the account works normally and shows the date. `DELETE /api/v1/account/deletion` cancels it; nothing leaves Network before the date.
+ - At the date, one Network transaction erases what Network owns:
+ - linked providers, sessions and devices, OAuth tokens, codes and grants;
+ - developer projects the person alone owns, with their credentials;
+ - user modules, follows both ways, blocks both ways, notifications, preferences, push subscriptions, effects, username history and pending merge intents.
+ - The OpenCoins balance is closed with one ledger entry (`reason: account_deleted`); ledger rows stay.
+ - The user row becomes a tombstone: the username is released, and the email, password and profile are cleared. The subject is never reused, and it resolves as `deleted`, as do aliases merged into it.
+ - Tokens are revoked strictly (`network.user.token_valid_after`, reason `account_deleted`). Network then emits `network.account.deleted { deletion_id, subject, requested_at, deleted_at }` (visibility internal).
+ - **Each service erases its own rows about the subject in its own transaction**, once per `deletion_id`:
+ - content the person authored is removed, except that an item with other people's replies beneath it stays as an authorless tombstone, so the replies keep their place;
+ - likes, votes and reactions go, and cached counts are recomputed;
+ - moderation records about the person's actions keep the pseudonymous subject;
+ - money and ledger rows stay, pseudonymised (no name, email or free text of the person), for the billing policy's retention;
+ - media under a retention hold (ADR-006) is kept and counted as retained.
+
+ It then confirms with `POST /internal/account-deletions/:deletion_id/confirmations`, carrying `network.account-deletion-confirmation@1` and the capability `network.account.deletion.confirm`, with counts of what it erased and retained.
+ - Network records each confirmation. Staff see deletions still waiting on a service; Events redelivers until the service answers.
+- **Nothing is exported or deleted for anyone but the signed-in person.** Staff do not start either flow. A person who lost access recovers the account first (ADR-029's staff merge, or provider recovery), then uses the same flow.
+
+## Alternatives considered
+
+- **Network calls each service's export endpoint:** rejected. It needs a Network → service credential and a route in every service; pushing parts reuses the service → Network tokens every service already has.
+- **Immediate deletion:** rejected. A mistaken or coerced request cannot be undone, and services would erase before a person could change their mind.
+- **Deleting ledger rows:** rejected by ADR-012; pseudonymised rows keep the books balanced.
+
+## Migration consequences
+
+Network gains the `account_exports`, `account_export_parts`, `account_deletions` and `account_deletion_confirmations` tables, a users `deleted_at`, and an hourly due-deletion check. Contracts gain two events, four schemas and two capabilities. Every service that stores data about people consumes both events. Until a service does, its export part is missing (the export says so) and its deletion confirmation is outstanding (staff see it).
+
+## Rollback
+
+Stop offering export and deletion (the account page hides both cards). Scheduled deletions can be cancelled by staff before their date. A deletion already carried out is not reversible, by design.
+
+## Acceptance tests
+
+- Export: a job gathers every expected service's part, the zip holds only the person's data, a missing service makes it `partial` and says which one, another person cannot download it, and it expires after 7 days.
+- Deletion: it refuses without a fresh sign-in or the typed username, cancellation stops it, and nothing happens before the date.
+- At the date, Network's rows are erased, the subject resolves as `deleted`, the old tokens are refused, and the event is emitted once.
+- Each service's test erases its rows on `network.account.deleted`, keeps others' replies and money rows pseudonymised, confirms with counts, and applies a redelivery once.
@@ -36,3 +36,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
@@ -36,3 +36,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
| [ADR-030](ADR-030-follow-graph.md) | Follow graph: Network owns follows by subject, Live keeps a projection; expand/migrate/contract with reconciliation | Accepted 2026-09-26; migration pending |
| [ADR-030](ADR-030-follow-graph.md) | Follow graph: Network owns follows by subject, Live keeps a projection; expand/migrate/contract with reconciliation | Accepted 2026-09-26; migration pending |
| [ADR-031](ADR-031-s3-compatible-surface.md) | S3-compatible subset for Media: bucket = project namespace, object CRUD + ListObjectsV2 + presign, per-project keys | Accepted 2026-09-26; implementation pending |
| [ADR-031](ADR-031-s3-compatible-surface.md) | S3-compatible subset for Media: bucket = project namespace, object CRUD + ListObjectsV2 + presign, per-project keys | Accepted 2026-09-26; implementation pending |
| [ADR-032](ADR-032-containers.md) | Containers: platform services stay systemd units (one adapter in ovhost, a reserved `runtime` field); tenant code isolation belongs to Stage C | Accepted 2026-09-26 |
| [ADR-032](ADR-032-containers.md) | Containers: platform services stay systemd units (one adapter in ovhost, a reserved `runtime` field); tenant code isolation belongs to Stage C | Accepted 2026-09-26 |
+| [ADR-033](ADR-033-account-export-and-deletion.md) | Account export and deletion: services push their export parts to Network; deletion after a 30-day grace, each service erases its rows and confirms; money rows kept pseudonymised | Accepted 2026-09-27; implementation in progress |
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"id":"mrg_01JAB2C3D4E5F6G7H8J9K0MNPS","service":"live","received_at":"2026-09-27T21:01:00.000Z","replaced":false}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"id":"exp_01JAB2C3D4E5F6G7H8J9K0MNPS","service":"live","received_at":"2026-09-27T21:01:00.000Z","replaced":false}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","completed_at":"2026-09-27T21:05:00.000Z","erased":{"messages":-1}}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","completed_at":"2026-09-27T21:05:00.000Z","erased":{"messages":120,"dm_messages":8,"blocks":1},"retained":{"moderation_actions":2}}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"deletion_id":"del_01JAB2C3D4E5F6G7H8J9K0MNPT","status":"scheduled","requested_at":"2026-09-27T21:00:00.000Z"}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"deletion_id":"del_01JAB2C3D4E5F6G7H8J9K0MNPT","status":"scheduled","requested_at":"2026-09-27T21:00:00.000Z","delete_after":"2026-10-27T21:00:00.000Z"}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","files":[{"name":"avatar.png","content":"iVBOR"}]}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","files":[{"name":"../network/profile.json","content":{}}]}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","files":[{"name":"follows.json","content":[{"channel":"xena","since":"2026-01-02T03:04:05.000Z"}]},{"name":"profile.json","content":{"username":"keep","bio":"hi"}}],"truncated":[],"note":"Stream recordings are Media's part."}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"export_id":"exp_01JAB2C3D4E5F6G7H8J9K0MNPS","status":"done","requested_at":"2026-09-27T21:00:00.000Z","deadline":"2026-09-27T21:30:00.000Z","services":[]}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"export_id":"exp_01JAB2C3D4E5F6G7H8J9K0MNPS","status":"partial","requested_at":"2026-09-27T21:00:00.000Z","deadline":"2026-09-27T21:30:00.000Z","ready_at":"2026-09-27T21:30:02.000Z","expires_at":"2026-10-04T21:30:02.000Z","size_bytes":48213,"services":[{"service":"network","status":"received","files":9,"bytes":30100},{"service":"live","status":"received","files":6,"bytes":18113},{"service":"games","status":"missing"}]}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"deletion_id":"del_01JAB2C3D4E5F6G7H8J9K0MNPT","subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","username":"keep","requested_at":"2026-08-28T21:00:00.000Z","deleted_at":"2026-09-27T21:00:00.000Z"}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"deletion_id":"del_01JAB2C3D4E5F6G7H8J9K0MNPT","subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","requested_at":"2026-08-28T21:00:00.000Z","deleted_at":"2026-09-27T21:00:00.000Z"}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"deletion_id":"del_01JAB2C3D4E5F6G7H8J9K0MNPT","subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","aliases":["usr_01JAB2C3D4E5F6G7H8J9K0MNPR"],"requested_at":"2026-08-28T21:00:00.000Z","deleted_at":"2026-09-27T21:00:00.000Z"}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"export_id":"exp_01JAB2C3D4E5F6G7H8J9K0MNPS","subject":"42","requested_at":"2026-09-27T21:00:00.000Z","deadline":"2026-09-27T21:30:00.000Z"}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"export_id":"exp_01JAB2C3D4E5F6G7H8J9K0MNPS","subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","requested_at":"2026-09-27T21:00:00.000Z"}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{"export_id":"exp_01JAB2C3D4E5F6G7H8J9K0MNPS","subject":"usr_01JAB2C3D4E5F6G7H8J9K0MNPQ","requested_at":"2026-09-27T21:00:00.000Z","deadline":"2026-09-27T21:30:00.000Z"}
@@ -25379,6 +25379,379 @@
@@ -25379,6 +25379,379 @@
}
}
}
}
},
},
+ "network.account.export_requested": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.account.export_requested.v1.json",
+ "title": "NetworkAccountExportRequestedPayload",
+ "description": "network.account.export_requested v1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033). A person asked for a copy of their data. Each service that keeps data about people pushes its part before the deadline: POST /internal/account-exports/{export_id}/parts (network.account-export-part@1, capability network.account.export.contribute), with only the subject's own rows. A redelivery carries the same export_id; a second part from the same service replaces the first.",
+ "type": "object",
+ "required": [
+ "export_id",
+ "subject",
+ "requested_at",
+ "deadline"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "export_id": {
+ "type": "string",
+ "pattern": "^exp_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The export job; a redelivery carries the same id."
+ },
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person whose data is exported."
+ },
+ "requested_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "deadline": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Network builds the archive when every expected service has answered, or at this time (30 minutes after the request); a later part is refused."
+ }
+ }
+ }
+ },
+ "network.account.deleted": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/network.account.deleted.v1.json",
+ "title": "NetworkAccountDeletedPayload",
+ "description": "network.account.deleted v1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033). An account's 30-day grace ended and Network erased what it owns; the subject is a tombstone and is never reused. Each service erases its own rows about the subject once per deletion_id: authored content is removed (an item with other people's replies beneath it stays as an authorless tombstone), likes, votes and reactions go with counts recomputed, money and ledger rows stay pseudonymised, and media under a retention hold stays. It then confirms: POST /internal/account-deletions/{deletion_id}/confirmations (network.account-deletion-confirmation@1, capability network.account.deletion.confirm).",
+ "type": "object",
+ "required": [
+ "deletion_id",
+ "subject",
+ "requested_at",
+ "deleted_at"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "deletion_id": {
+ "type": "string",
+ "pattern": "^del_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The deletion; a redelivery carries the same id."
+ },
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The deleted account."
+ },
+ "aliases": {
+ "type": "array",
+ "maxItems": 50,
+ "items": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "description": "Subjects merged into this account earlier (ADR-029); a service that still keeps rows under one erases them too."
+ },
+ "requested_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the person scheduled the deletion."
+ },
+ "deleted_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "network.account-export": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/account-export.v1.json",
+ "title": "NetworkAccountExport",
+ "description": "network.account-export@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): a person's data export job. POST /api/v1/account/export starts one (one open at a time, one a day) and GET /api/v1/account/export/{id} answers its state. When ready or partial, GET /api/v1/account/export/{id}/download serves the zip to the same person until expires_at.",
+ "type": "object",
+ "required": [
+ "export_id",
+ "status",
+ "requested_at",
+ "deadline",
+ "services"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "export_id": {
+ "type": "string",
+ "pattern": "^exp_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "status": {
+ "enum": [
+ "pending",
+ "ready",
+ "partial",
+ "expired",
+ "failed"
+ ],
+ "description": "pending: waiting for the services' parts. ready: every expected service answered. partial: the deadline passed without some (services[].status missing). expired: the archive is gone (7 days). failed: the archive could not be built."
+ },
+ "requested_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "deadline": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "ready_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "When the archive was built."
+ },
+ "expires_at": {
+ "type": "string",
+ "format": "date-time",
+ "description": "The archive is deleted then (7 days after ready_at)."
+ },
+ "size_bytes": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "services": {
+ "type": "array",
+ "maxItems": 64,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "service",
+ "status"
+ ],
+ "properties": {
+ "service": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,31}$"
+ },
+ "status": {
+ "enum": [
+ "received",
+ "waiting",
+ "missing"
+ ]
+ },
+ "files": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "bytes": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "network.account-export-part": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/account-export-part.v1.json",
+ "title": "NetworkAccountExportPart",
+ "description": "network.account-export-part@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service's part of a person's data export, POST /internal/account-exports/{export_id}/parts with capability network.account.export.contribute (a token speaks only for its own service). Files are JSON documents of the subject's own rows (never another person's data), at most 20 MB in all; each lands in the archive as <service>/<name>. A second part from the same service replaces the first.",
+ "type": "object",
+ "required": [
+ "subject",
+ "files"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "Must be the export's subject."
+ },
+ "files": {
+ "type": "array",
+ "maxItems": 64,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "name",
+ "content"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_.-]{0,62}\\.json$",
+ "description": "A flat file name ending in .json."
+ },
+ "content": {
+ "description": "Any JSON value: the rows, as the service describes them."
+ }
+ }
+ }
+ },
+ "truncated": {
+ "type": "array",
+ "maxItems": 64,
+ "items": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "description": "Files cut at the service's own row limit, named so the README says so."
+ },
+ "note": {
+ "type": "string",
+ "maxLength": 500,
+ "description": "A line for the README, such as where media files are downloaded."
+ }
+ }
+ }
+ },
+ "network.account-deletion": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/account-deletion.v1.json",
+ "title": "NetworkAccountDeletion",
+ "description": "network.account-deletion@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): a person's scheduled account deletion. POST /api/v1/account/deletion (a fresh sign-in, auth_time within 10 minutes, and the username typed out) schedules it 30 days ahead; GET answers it; DELETE cancels it before delete_after.",
+ "type": "object",
+ "required": [
+ "deletion_id",
+ "status",
+ "requested_at",
+ "delete_after"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "deletion_id": {
+ "type": "string",
+ "pattern": "^del_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "status": {
+ "enum": [
+ "scheduled",
+ "cancelled",
+ "deleted"
+ ]
+ },
+ "requested_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "delete_after": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Nothing leaves Network before this time; the account works normally until then."
+ },
+ "cancelled_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "deleted_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "network.account-deletion-confirmation": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/account-deletion-confirmation.v1.json",
+ "title": "NetworkAccountDeletionConfirmation",
+ "description": "network.account-deletion-confirmation@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service confirms it erased a deleted account's rows, POST /internal/account-deletions/{deletion_id}/confirmations with capability network.account.deletion.confirm (a token speaks only for its own service). Counts only: what it erased and what it keeps (money and ledger rows pseudonymised, media under a retention hold, tombstones that keep others' replies in place). A repeat confirmation replaces the first.",
+ "type": "object",
+ "required": [
+ "subject",
+ "completed_at",
+ "erased"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "Must be the deletion's subject."
+ },
+ "completed_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "erased": {
+ "type": "object",
+ "maxProperties": 64,
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Rows erased, by kind (messages, follows, pastes, …)."
+ },
+ "retained": {
+ "type": "object",
+ "maxProperties": 64,
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Rows kept, by kind, each pseudonymised or held (ledger, held_media, tombstones, …)."
+ }
+ }
+ }
+ },
+ "network.account-data-receipt": {
+ "version": "1.0.0",
+ "owner": "network",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/network/account-data-receipt.v1.json",
+ "title": "NetworkAccountDataReceipt",
+ "description": "network.account-data-receipt@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): Network's answer to a service's export part (network.account.export.contribute) or deletion confirmation (network.account.deletion.confirm). replaced: this service had already sent one for the same job, and this one replaced it.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "service",
+ "received_at",
+ "replaced"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^(exp|del)_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The export or deletion."
+ },
+ "service": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,31}$"
+ },
+ "received_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "replaced": {
+ "type": "boolean"
+ }
+ }
+ }
+ },
"network.subject.merged": {
"network.subject.merged": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "network",
"owner": "network",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.AI API",
"title": "OpenVibe.AI API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Billing API",
"title": "OpenVibe.Billing API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Blog API",
"title": "OpenVibe.Blog API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Chat API",
"title": "OpenVibe.Chat API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Codes API",
"title": "OpenVibe.Codes API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Community API",
"title": "OpenVibe.Community API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Coupons API",
"title": "OpenVibe.Coupons API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Deals API",
"title": "OpenVibe.Deals API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Events API",
"title": "OpenVibe.Events API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Games API",
"title": "OpenVibe.Games API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Host API",
"title": "OpenVibe.Host API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -96,8 +96,8 @@
@@ -96,8 +96,8 @@
"service": "network",
"service": "network",
"name": "OpenVibe.Network",
"name": "OpenVibe.Network",
"origin": "https://openvibe.network",
"origin": "https://openvibe.network",
- "operations": 18,
- "capabilities": 15
+ "operations": 20,
+ "capabilities": 17
},
},
{
{
"service": "news",
"service": "news",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Live API",
"title": "OpenVibe.Live API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Media API",
"title": "OpenVibe.Media API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Network API",
"title": "OpenVibe.Network API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -304,6 +304,138 @@
@@ -304,6 +304,138 @@
}
}
}
}
},
},
+ "/internal/account-deletions/{deletion_id}/confirmations": {
+ "post": {
+ "operationId": "post_internal_account_deletions_deletion_id_confirmations",
+ "summary": "Confirm that a deleted account's rows are erased at a service (ADR-033), with counts of what was erased and what is kept (pseudonymised money rows, held media, tombstones).",
+ "description": "**network.account.deletion.confirm** (internal): Confirm that a deleted account's rows are erased at a service (ADR-033), with counts of what was erased and what is kept (pseudonymised money rows, held media, tombstones). A token may only speak for its own service.",
+ "tags": [
+ "network.account"
+ ],
+ "x-openvibe-capabilities": [
+ "network.account.deletion.confirm"
+ ],
+ "x-openvibe-visibility": [
+ "internal"
+ ],
+ "x-openvibe-quota-class": "account-data",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.account.deletion.confirm"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.account-deletion-confirmation.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "deletion_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.account-data-receipt.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/internal/account-exports/{export_id}/parts": {
+ "post": {
+ "operationId": "post_internal_account_exports_export_id_parts",
+ "summary": "Push a service's part of a person's data export (ADR-033): the subject's own rows as JSON files, before the export's deadline.",
+ "description": "**network.account.export.contribute** (internal): Push a service's part of a person's data export (ADR-033): the subject's own rows as JSON files, before the export's deadline. A token may only speak for its own service; a second part replaces the first.",
+ "tags": [
+ "network.account"
+ ],
+ "x-openvibe-capabilities": [
+ "network.account.export.contribute"
+ ],
+ "x-openvibe-visibility": [
+ "internal"
+ ],
+ "x-openvibe-quota-class": "account-data",
+ "security": [
+ {
+ "openvibeToken": [
+ "network.account.export.contribute"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.account-export-part.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "export_id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/network.account-data-receipt.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/internal/blocks?subject=usr_…": {
"/internal/blocks?subject=usr_…": {
"get": {
"get": {
"operationId": "get_internal_blocks_subject_usr",
"operationId": "get_internal_blocks_subject_usr",
@@ -1438,6 +1570,129 @@
@@ -1438,6 +1570,129 @@
}
}
}
}
},
},
+ "network.account-data-receipt.v1": {
+ "title": "NetworkAccountDataReceipt",
+ "description": "network.account-data-receipt@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): Network's answer to a service's export part (network.account.export.contribute) or deletion confirmation (network.account.deletion.confirm). replaced: this service had already sent one for the same job, and this one replaced it.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "service",
+ "received_at",
+ "replaced"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^(exp|del)_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The export or deletion."
+ },
+ "service": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9-]{1,31}$"
+ },
+ "received_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "replaced": {
+ "type": "boolean"
+ }
+ }
+ },
+ "network.account-deletion-confirmation.v1": {
+ "title": "NetworkAccountDeletionConfirmation",
+ "description": "network.account-deletion-confirmation@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service confirms it erased a deleted account's rows, POST /internal/account-deletions/{deletion_id}/confirmations with capability network.account.deletion.confirm (a token speaks only for its own service). Counts only: what it erased and what it keeps (money and ledger rows pseudonymised, media under a retention hold, tombstones that keep others' replies in place). A repeat confirmation replaces the first.",
+ "type": "object",
+ "required": [
+ "subject",
+ "completed_at",
+ "erased"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "Must be the deletion's subject."
+ },
+ "completed_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "erased": {
+ "type": "object",
+ "maxProperties": 64,
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Rows erased, by kind (messages, follows, pastes, …)."
+ },
+ "retained": {
+ "type": "object",
+ "maxProperties": 64,
+ "additionalProperties": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "description": "Rows kept, by kind, each pseudonymised or held (ledger, held_media, tombstones, …)."
+ }
+ }
+ },
+ "network.account-export-part.v1": {
+ "title": "NetworkAccountExportPart",
+ "description": "network.account-export-part@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service's part of a person's data export, POST /internal/account-exports/{export_id}/parts with capability network.account.export.contribute (a token speaks only for its own service). Files are JSON documents of the subject's own rows (never another person's data), at most 20 MB in all; each lands in the archive as <service>/<name>. A second part from the same service replaces the first.",
+ "type": "object",
+ "required": [
+ "subject",
+ "files"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "Must be the export's subject."
+ },
+ "files": {
+ "type": "array",
+ "maxItems": 64,
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "name",
+ "content"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9_.-]{0,62}\\.json$",
+ "description": "A flat file name ending in .json."
+ },
+ "content": {
+ "description": "Any JSON value: the rows, as the service describes them."
+ }
+ }
+ }
+ },
+ "truncated": {
+ "type": "array",
+ "maxItems": 64,
+ "items": {
+ "type": "string",
+ "maxLength": 64
+ },
+ "description": "Files cut at the service's own row limit, named so the README says so."
+ },
+ "note": {
+ "type": "string",
+ "maxLength": 500,
+ "description": "A line for the README, such as where media files are downloaded."
+ }
+ }
+ },
"network.blocks-result.v1": {
"network.blocks-result.v1": {
"title": "NetworkBlocksResult",
"title": "NetworkBlocksResult",
"description": "network.blocks-result@1: the answer of network.blocks.read on OpenVibe.Network. GET /internal/blocks?subject=usr_… (a service token holding network.blocks.read; the shared internal key is refused) → { subject, blocks, blocked_by }: the people the subject has blocked and the people who have blocked the subject, as usr_ subjects in subject order, active blocks only (a person may hold at most 5000). Answered with Cache-Control: no-store. A subject that is not a usr_ id is refused with 400 blocks.bad_subject (problem+json).",
"description": "network.blocks-result@1: the answer of network.blocks.read on OpenVibe.Network. GET /internal/blocks?subject=usr_… (a service token holding network.blocks.read; the shared internal key is refused) → { subject, blocks, blocked_by }: the people the subject has blocked and the people who have blocked the subject, as usr_ subjects in subject order, active blocks only (a person may hold at most 5000). Answered with Cache-Control: no-store. A subject that is not a usr_ id is refused with 400 blocks.bad_subject (problem+json).",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.News API",
"title": "OpenVibe.News API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenRe.Stream API",
"title": "OpenRe.Stream API",
- "version": "0.70.0",
- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Reviews API",
"title": "OpenVibe.Reviews API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Search API",
"title": "OpenVibe.Search API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Sources API",
"title": "OpenVibe.Sources API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tips API",
"title": "OpenVibe.Tips API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tools API",
"title": "OpenVibe.Tools API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Trade API",
"title": "OpenVibe.Trade API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.VIP API",
"title": "OpenVibe.VIP API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Wiki API",
"title": "OpenVibe.Wiki API",
- "version": "0.70.0",
- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.70.0): each operation lists the capabilities it performs; a token for 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.71.0",
+ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.71.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -28636,6 +28636,174 @@ export interface NetworkPrincipalGrantChangedPayload {
@@ -28636,6 +28636,174 @@ export interface NetworkPrincipalGrantChangedPayload {
actor_subject?: string | null;
actor_subject?: string | null;
}
}
+/** [email protected] (owner: network) */
+/**
+ * network.account.export_requested v1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033). A person asked for a copy of their data. Each service that keeps data about people pushes its part before the deadline: POST /internal/account-exports/{export_id}/parts (network.account-export-part@1, capability network.account.export.contribute), with only the subject's own rows. A redelivery carries the same export_id; a second part from the same service replaces the first.
+ */
+export interface NetworkAccountExportRequestedPayload {
+ /**
+ * The export job; a redelivery carries the same id.
+ */
+ export_id: string;
+ /**
+ * The person whose data is exported.
+ */
+ subject: string;
+ requested_at: string;
+ /**
+ * Network builds the archive when every expected service has answered, or at this time (30 minutes after the request); a later part is refused.
+ */
+ deadline: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.account.deleted v1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033). An account's 30-day grace ended and Network erased what it owns; the subject is a tombstone and is never reused. Each service erases its own rows about the subject once per deletion_id: authored content is removed (an item with other people's replies beneath it stays as an authorless tombstone), likes, votes and reactions go with counts recomputed, money and ledger rows stay pseudonymised, and media under a retention hold stays. It then confirms: POST /internal/account-deletions/{deletion_id}/confirmations (network.account-deletion-confirmation@1, capability network.account.deletion.confirm).
+ */
+export interface NetworkAccountDeletedPayload {
+ /**
+ * The deletion; a redelivery carries the same id.
+ */
+ deletion_id: string;
+ /**
+ * The deleted account.
+ */
+ subject: string;
+ /**
+ * Subjects merged into this account earlier (ADR-029); a service that still keeps rows under one erases them too.
+ *
+ * @maxItems 50
+ */
+ aliases?: string[];
+ /**
+ * When the person scheduled the deletion.
+ */
+ requested_at: string;
+ deleted_at: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.account-export@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): a person's data export job. POST /api/v1/account/export starts one (one open at a time, one a day) and GET /api/v1/account/export/{id} answers its state. When ready or partial, GET /api/v1/account/export/{id}/download serves the zip to the same person until expires_at.
+ */
+export interface NetworkAccountExport {
+ export_id: string;
+ /**
+ * pending: waiting for the services' parts. ready: every expected service answered. partial: the deadline passed without some (services[].status missing). expired: the archive is gone (7 days). failed: the archive could not be built.
+ */
+ status: "pending" | "ready" | "partial" | "expired" | "failed";
+ requested_at: string;
+ deadline: string;
+ /**
+ * When the archive was built.
+ */
+ ready_at?: string;
+ /**
+ * The archive is deleted then (7 days after ready_at).
+ */
+ expires_at?: string;
+ size_bytes?: number;
+ /**
+ * @maxItems 64
+ */
+ services: {
+ service: string;
+ status: "received" | "waiting" | "missing";
+ files?: number;
+ bytes?: number;
+ }[];
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.account-export-part@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service's part of a person's data export, POST /internal/account-exports/{export_id}/parts with capability network.account.export.contribute (a token speaks only for its own service). Files are JSON documents of the subject's own rows (never another person's data), at most 20 MB in all; each lands in the archive as <service>/<name>. A second part from the same service replaces the first.
+ */
+export interface NetworkAccountExportPart {
+ /**
+ * Must be the export's subject.
+ */
+ subject: string;
+ /**
+ * @maxItems 64
+ */
+ files: {
+ /**
+ * A flat file name ending in .json.
+ */
+ name: string;
+ /**
+ * Any JSON value: the rows, as the service describes them.
+ */
+ content: {
+ [k: string]: unknown | undefined;
+ };
+ }[];
+ /**
+ * Files cut at the service's own row limit, named so the README says so.
+ *
+ * @maxItems 64
+ */
+ truncated?: string[];
+ /**
+ * A line for the README, such as where media files are downloaded.
+ */
+ note?: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.account-deletion@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): a person's scheduled account deletion. POST /api/v1/account/deletion (a fresh sign-in, auth_time within 10 minutes, and the username typed out) schedules it 30 days ahead; GET answers it; DELETE cancels it before delete_after.
+ */
+export interface NetworkAccountDeletion {
+ deletion_id: string;
+ status: "scheduled" | "cancelled" | "deleted";
+ requested_at: string;
+ /**
+ * Nothing leaves Network before this time; the account works normally until then.
+ */
+ delete_after: string;
+ cancelled_at?: string;
+ deleted_at?: string;
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.account-deletion-confirmation@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): one service confirms it erased a deleted account's rows, POST /internal/account-deletions/{deletion_id}/confirmations with capability network.account.deletion.confirm (a token speaks only for its own service). Counts only: what it erased and what it keeps (money and ledger rows pseudonymised, media under a retention hold, tombstones that keep others' replies in place). A repeat confirmation replaces the first.
+ */
+export interface NetworkAccountDeletionConfirmation {
+ /**
+ * Must be the deletion's subject.
+ */
+ subject: string;
+ completed_at: string;
+ /**
+ * Rows erased, by kind (messages, follows, pastes, …).
+ */
+ erased: {
+ [k: string]: number | undefined;
+ };
+ /**
+ * Rows kept, by kind, each pseudonymised or held (ledger, held_media, tombstones, …).
+ */
+ retained?: {
+ [k: string]: number | undefined;
+ };
+}
+
+/** [email protected] (owner: network) */
+/**
+ * network.account-data-receipt@1 (OpenVibe.Network; roadmap WS-B task 7, ADR-033): Network's answer to a service's export part (network.account.export.contribute) or deletion confirmation (network.account.deletion.confirm). replaced: this service had already sent one for the same job, and this one replaced it.
+ */
+export interface NetworkAccountDataReceipt {
+ /**
+ * The export or deletion.
+ */
+ id: string;
+ service: string;
+ received_at: string;
+ replaced: boolean;
+}
+
/** [email protected] (owner: network) */
/** [email protected] (owner: network) */
/**
/**
* network.subject.merged v1 (OpenVibe.Network; roadmap WS-B task 5, ADR-029). Two accounts became one: `from`, the folded-in subject, is now an alias of `into`, the survivor, which keeps its subject, username, profile and settings. Network has already moved what it owns (linked providers, sessions, OAuth grants, developer projects, OpenCoins and user modules) in the transaction that writes this event to its outbox. Every service that stores subjects repoints its own rows from `from` to `into` in its own transaction and keeps no copy under `from`; a row it cannot move (a unique conflict, such as both accounts following the same channel) keeps the survivor's row and drops the other, as a counted, logged outcome. Until a service has applied it, its rows stay under `from`, which still resolves to `into` through Network's aliases. Apply once per merge_id. Envelope: subject { type: user, id: <into> }, visibility internal, actor the person who merged (or the staff member, for an account-recovery merge).
* network.subject.merged v1 (OpenVibe.Network; roadmap WS-B task 5, ADR-029). Two accounts became one: `from`, the folded-in subject, is now an alias of `into`, the survivor, which keeps its subject, username, profile and settings. Network has already moved what it owns (linked providers, sessions, OAuth grants, developer projects, OpenCoins and user modules) in the transaction that writes this event to its outbox. Every service that stores subjects repoints its own rows from `from` to `into` in its own transaction and keeps no copy under `from`; a row it cannot move (a unique conflict, such as both accounts following the same channel) keeps the survivor's row and drops the other, as a counted, logged outcome. Until a service has applied it, its rows stay under `from`, which still resolves to `into` through Network's aliases. Apply once per merge_id. Envelope: subject { type: user, id: <into> }, visibility internal, actor the person who merged (or the staff member, for an account-recovery merge).
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "network.account.deletion.confirm",
+ "version": "1.0",
+ "owner": "network",
+ "status": "active",
+ "visibility": "internal",
+ "description": "Confirm that a deleted account's rows are erased at a service (ADR-033), with counts of what was erased and what is kept (pseudonymised money rows, held media, tombstones). A token may only speak for its own service.",
+ "permissions": [
+ "account:delete"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "account-data",
+ "events": [],
+ "implementedBy": [
+ "POST /internal/account-deletions/:deletion_id/confirmations"
+ ],
+ "inputSchema": "network.account-deletion-confirmation@1",
+ "outputSchema": "network.account-data-receipt@1"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "network.account.export.contribute",
+ "version": "1.0",
+ "owner": "network",
+ "status": "active",
+ "visibility": "internal",
+ "description": "Push a service's part of a person's data export (ADR-033): the subject's own rows as JSON files, before the export's deadline. A token may only speak for its own service; a second part replaces the first.",
+ "permissions": [
+ "account:export"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "account-data",
+ "events": [],
+ "implementedBy": [
+ "POST /internal/account-exports/:export_id/parts"
+ ],
+ "inputSchema": "network.account-export-part@1",
+ "outputSchema": "network.account-data-receipt@1"
+}
@@ -25,6 +25,8 @@
@@ -25,6 +25,8 @@
"chat.room.message.deleted"
"chat.room.message.deleted"
],
],
"eventsConsumed": [
"eventsConsumed": [
+ "network.account.deleted",
+ "network.account.export_requested",
"network.block.changed",
"network.block.changed",
"network.user.token_valid_after"
"network.user.token_valid_after"
],
],
@@ -38,6 +38,8 @@
@@ -38,6 +38,8 @@
"community.thread.created"
"community.thread.created"
],
],
"eventsConsumed": [
"eventsConsumed": [
+ "network.account.deleted",
+ "network.account.export_requested",
"network.block.changed",
"network.block.changed",
"network.user.token_valid_after"
"network.user.token_valid_after"
],
],
@@ -47,6 +47,8 @@
@@ -47,6 +47,8 @@
"games.moderation.action"
"games.moderation.action"
],
],
"eventsConsumed": [
"eventsConsumed": [
+ "network.account.deleted",
+ "network.account.export_requested",
"network.user.token_valid_after"
"network.user.token_valid_after"
],
],
"namespacesOwned": [
"namespacesOwned": [
@@ -51,6 +51,8 @@
@@ -51,6 +51,8 @@
"media.clip.*",
"media.clip.*",
"media.storage.*",
"media.storage.*",
"media.vod.*",
"media.vod.*",
+ "network.account.deleted",
+ "network.account.export_requested",
"network.user.token_valid_after",
"network.user.token_valid_after",
"network.user.updated",
"network.user.updated",
"openre.session.*"
"openre.session.*"
@@ -59,6 +59,8 @@
@@ -59,6 +59,8 @@
"media.vod.ready"
"media.vod.ready"
],
],
"eventsConsumed": [
"eventsConsumed": [
+ "network.account.deleted",
+ "network.account.export_requested",
"network.user.token_valid_after"
"network.user.token_valid_after"
],
],
"namespacesOwned": [
"namespacesOwned": [
@@ -34,6 +34,8 @@
@@ -34,6 +34,8 @@
"capabilities": [
"capabilities": [
"identity.subject.resolve",
"identity.subject.resolve",
"mods.grant.manage",
"mods.grant.manage",
+ "network.account.deletion.confirm",
+ "network.account.export.contribute",
"network.analytics.creator.read",
"network.analytics.creator.read",
"network.blocks.read",
"network.blocks.read",
"network.coins.credit",
"network.coins.credit",
@@ -60,6 +62,8 @@
@@ -60,6 +62,8 @@
"network.follow.created",
"network.follow.created",
"network.follow.deleted",
"network.follow.deleted",
"network.subject.merged",
"network.subject.merged",
+ "network.account.export_requested",
+ "network.account.deleted",
"network.grant.changed",
"network.grant.changed",
"network.module.updated",
"network.module.updated",
"network.notification.created",
"network.notification.created",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.70.0",
+ "version": "0.71.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
"types": "generated/typescript/index.d.ts",