Mirror of github.com/OpenVibers/OpenVibe.Contracts
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-27 04:21:09 -07:00
.github/workflows Generate the plan's estate table and repository manifests (#42) 2026-10-04 21:14:49 -07:00
bin v0.34.3: openvibe-contracts-check skips *.test.* and *.spec.* files (tests mint tokens for other owners on purpose; Games keeps tests next to the code) 2026-09-23 19:56:25 -07:00
compatibility Contracts 0.150.0: Host's engine ids retired (Space serves space.*); OpenVibe.Video is live (#106) 2026-10-12 04:08:42 -07:00
contracts 0.216.0: browser desks, kind desk takes kind (desktop | browser) on start (Desks plan D2, Node v0.23.0) (#223) 2026-10-27 04:21:09 -07:00
docs ESTATE.md and repository manifests regenerated: every repository names contracts v0.214.1, Node 0.18.0 (#221) 2026-10-27 02:43:34 -07:00
fixtures 0.216.0: browser desks, kind desk takes kind (desktop | browser) on start (Desks plan D2, Node v0.23.0) (#223) 2026-10-27 04:21:09 -07:00
generated 0.216.0: browser desks, kind desk takes kind (desktop | browser) on start (Desks plan D2, Node v0.23.0) (#223) 2026-10-27 04:21:09 -07:00
governance Contracts: the product at openre.stream is OpenRestream (owner, 2026-10-09) (#75) 2026-10-09 15:31:49 -07:00
lib Contracts 0.176.0: person-owned resources can be controlled (common.owned-resource-control-request@1; bot, chat, openre, community resource.control) (#134) 2026-10-14 19:15:07 -07:00
manifests ESTATE.md and repository manifests regenerated: every repository names contracts v0.214.1, Node 0.18.0 (#221) 2026-10-27 02:43:34 -07:00
scripts The docs check compares facts, not only headings and dates: versions, pins, consumer lists, deployed flags, README links and named paths and migrations; repositories with no manifest need a STATUS.json (it said 44/44 current while Sources reported 0 items) (#203) 2026-10-22 09:14:53 -07:00
test The docs check compares facts, not only headings and dates: versions, pins, consumer lists, deployed flags, README links and named paths and migrations; repositories with no manifest need a STATUS.json (it said 44/44 current while Sources reported 0 items) (#203) 2026-10-22 09:14:53 -07:00
.gitignore v0.1.0: first contracts (subject, legacy identity map, service-token claims, entity ref, problem, service manifest, capability, event envelope, media ref) 2026-09-22 14:08:20 -07:00
.gitleaks.toml Drop the gitleaks allowlist for the retired resize idempotency key 2026-10-04 16:55:47 -07:00
.gitleaksignore Ignore seven proven sample values by fingerprint in the full-history secrets scan 2026-10-04 16:54:38 -07:00
CHANGELOG.md 0.216.0: browser desks, kind desk takes kind (desktop | browser) on start (Desks plan D2, Node v0.23.0) (#223) 2026-10-27 04:21:09 -07:00
index.js Use a low-entropy sample idempotency key in the resize fixture 2026-10-04 16:51:31 -07:00
LICENSE Charter: OpenVibe.Contracts as defined in the OpenVibe realignment plan (placeholder, no code yet) 2026-09-21 09:18:47 -07:00
package-lock.json 0.214.1: site text says what Games, Tools and Robots do today (the creator platform, 180+ tools, keyboard/gamepad/joystick driving) (#220) 2026-10-27 00:36:16 -07:00
package.json 0.216.0: browser desks, kind desk takes kind (desktop | browser) on start (Desks plan D2, Node v0.23.0) (#223) 2026-10-27 04:21:09 -07:00
README.md 0.210.0: Sources categories housing, food and jobs; Bot's catalog names only what it runs (no ESP32, RTSP or hardware for sale), Actor's real free tier, Help without 'coming' (#205) 2026-10-22 12:20:55 -07:00
SECURITY.md SECURITY.md: how to report a vulnerability ([email protected], 7-day reply, scope, supported versions) 2026-09-23 19:27:28 -07:00
STATUS.json 0.210.0: Sources categories housing, food and jobs; Bot's catalog names only what it runs (no ESP32, RTSP or hardware for sale), Actor's real free tier, Help without 'coming' (#205) 2026-10-22 12:20:55 -07:00

OpenVibe.Contracts

Machine-readable contracts for the whole OpenVibe network.

Status: alpha (first released in Wave 1; the release is package.json's version, tagged). At v0.210.0: 798 schemas in contracts/catalog.json, 45 service manifests, 348 capability manifests, 13 namespace manifests and the staff capability map. Service repositories run openvibe-contracts-check and the pin-drift check in CI, and deployed services verify service and app tokens with serviceAuth in production. Consumers pin release tags (each repository's STATUS.json names its pin). node scripts/docs-currency.js <root> checks every checkout's README and STATUS.json against its code: the pins and versions they name, a library's consumer list, the deployed flag against the manifest, README links, and the paths and migrations they name. node scripts/estate.js generates docs/ESTATE.md and manifests/repositories/ from the estate checkouts, and --check fails on drift (plan T1 step 3).

Plan: OpenVibe Development Roadmap, Wave 1 (implementation plan rev 3, §3.1 and §18.11).
License: AGPL-3.0 (same as every OpenVibe service).

Using it

Pin a release tag. It's a public repo, so no deploy key is needed:

"dependencies": { "openvibe-contracts": "github:OpenVibers/OpenVibe.Contracts#v0.1.0" }
const contracts = require('openvibe-contracts');

contracts.validate('identity.subject-ref@1', { type: 'user', id: 'usr_01JAB2C3D4E5F6G7H8J9K0MNPQ' });  // { valid, errors }
contracts.ids.newId('user');                         // 'usr_01J…' (ULID, time-sortable)
contracts.ids.parseSubject('service:live');          // { type: 'service', id: 'live' }

app.use(contracts.http.middleware());                // req.ov = { traceId, requestId, traceparent, idempotencyKey }
contracts.http.sendProblem(res, 403, 'capability.denied', { detail: 'not granted', ctx: req.ov });

contracts.capabilities.check(tokenClaims, 'media.object.upload', { namespace: 'live.files' });
// { allowed: false, code: 'capability.denied' | 'capability.namespace_denied' | 'capability.unknown', reason }

Service principals (v0.2): a caller gets a short-lived token from Network with its OAuth client credentials, and a receiver guards a route with the capability it performs:

const tokens = contracts.serviceAuth.createTokenClient({ tokenUrl: `${NETWORK}/oauth/token`, clientId, clientSecret, audience: 'openvibe.network' });
await fetch(url, { headers: await tokens.authHeaders() });

router.post('/coins/credit', contracts.serviceAuth.requireCapability('network.coins.credit', {
    publicKey, issuer, audience: 'openvibe.network',
    legacy: (req) => req.headers['x-internal-key'] === key,   // old callers keep working during migration
}), handler);

A request that presents a Bearer token is judged only on that token; a bad token is never rescued by a legacy header.

Contract check for a service's CI (v0.3): npx openvibe-contracts-check --service network --src server. It fails if the installed contracts are outside the service manifest's contractRanges, if the code enforces a capability (requireCapability('…'), guard('…'), capabilities.check(…, '…')) that is undefined, retired or owned by another service, or if it validates against a contract id that doesn't exist.

TypeScript types: generated/typescript/index.d.ts ("types" in package.json). All schemas in one file: generated/json-schema/bundle.json.

OpenAPI 3.1 per service: generated/openapi/<service>.json (index in generated/openapi/index.json; require('openvibe-contracts').openapi.document('tools')). Each is built from the capabilities (implementedBy routes, input and output schemas) by lib/openapi.js. It lists every route a capability names, with the capabilities it performs, its request and response schemas and problem+json errors. OpenVibe.Codes serves them together as the API explorer.

What's in v0.1

Contract Owner Purpose
identity.subject-ref network { type: user|guest|service|app|mod|system, id }: the one actor reference; never a service-local integer
identity.legacy-identity-map network row shape of identity_legacy_map: legacy id to subject, kept alongside legacy ids
identity.service-token-claims network client-credentials token for service/app/mod principals (replaces X-Internal-Key)
common.entity-ref contracts { service, type, id, revision?, label? }: typed cross-service reference instead of a foreign key
errors.problem contracts RFC 9457 problem details plus stable code, request_id, trace_id and a compatibility error field
registry.service-manifest network what a service is, where it lives and what it offers
capabilities.capability network an invokable action: permissions, resource constraints, quota class, events
events.event-envelope events durable event shape for OpenVibe.Events (Wave 3)
media.media-ref media med_<ULID> (Wave 4) or transitional legacy:<app>:<kind>:<id>

Manifests: manifests/services/ (45 services at v0.210.0: 39 alpha, 1 beta (Media), 4 stable (Games, Live, Network, Tools) and 1 retired (Sites, 2026-10-14); OpenVibe.Realtime has no manifest, as ADR-005 closed it before it was built; Contracts, SDK, Shared and Publishing are released libraries and Examples a repository with CI, none of them a runtime; docs/ESTATE.md tables every repository), manifests/capabilities/ (348 capabilities at v0.210.0: 306 active, 27 planned and 15 retired; the v0.1 set was Media upload/read, chat send, paste create, coins credit/debit, notifications push, subject resolve and Community post). Each active capability names the route that implements it today. Where a service is reachable today (public, loopback only, library) is Network's observed overlay (server/registry/exposure.js there); manifest notes say whether a service is deployed and launched publicly.

Ids. Subjects use prefixed ULIDs: usr_, gst_, app_, mod_. Services and system actors use slugs (live, media). Events use evt_ and Media objects med_.

User modules (v0.4)

manifests/products/<domain>.json (registry.product@1) is the product catalog the OpenVibe.Sites repository used to hold (plan T11 lane D): one file per product domain that no service manifest presents as a site (planned products, a service's sub-domains and extra brand domains, closed or moved addresses), with its tld, accent, description, pillars, FAQ, keywords, vision, launch requirement and relationships. A service manifest whose site carries tld (registry.service-manifest 1.1.0) is the catalog home of its own domain instead. contracts.products.catalog() lists both homes; every domain has exactly one (test/product-catalog.test.js, against the 33-domain Sites snapshot in test/fixtures/sites-domains.json).

manifests/namespaces/*.json define per-subject module namespaces (modules.namespace@1): owner, data schema, writers (owner service and/or the user), public fields, quota and what happens when the owner is retired. OpenVibe.Network stores the records (modules.module-record@1, revision-checked writes). contracts.modules.validateData / publicView / canWrite apply the same rules everywhere. Modules hold portable preferences and summaries, never domain truth, money or authoritative game inventory.

Every change to a record is announced as network.module.updated (v0.32): the revision after the change, the changed field names and only the new values of changed public fields. Removing an account deletes its records; merging two keeps the survivor's record where both have one and moves the rest. onOwnerRemoved is about the owning service being retired, not the person.

Developer-app events (v0.28)

Three public capabilities let a developer app (ADR-014) use OpenVibe.Events with a scoped app token, without any first-party events.* capability (those stay internal):

Capability What the app may do
events.app.publish publish event types app.<project_key>.<name>[.<more>] only
events.app.read pull its own project's events (same environment) plus first-party public events
events.app.subscribe webhook subscriptions in the same scope, to public https endpoints only

project_key is p followed by the project's ULID in lowercase (prj_01JAB… → p01jab…), so it fits an event-type segment. An app event's source is app- followed by the app's ULID in lowercase (app:app_01JAB… → app-01jab…), which keeps events.event-envelope@1 unchanged (its source pattern already allows it). OpenVibe.Events enforces the scope, the sandbox separation and per-project quotas.

Event payload contracts (v0.30)

An event type's payload contract is named after the type. The envelope's version is the contract's major, and the file is contracts/events/payloads/<event_type>.v<version>.json. The owner is the producing service, whose manifest lists the type in eventsProduced. Contracts with status planned describe events the owner does not emit yet (ai.run.*). tools.job.* are active since v0.33.0, because Tools publishes them. v0.34.0 added News, Reviews, Coupons, Deals, Trade, Games and media.object.deleted|visibility_changed (51, all emitted today). Listed events still without a contract: chat.message.created, chat.dm.created, chat.moderation.action, codes.app.* (3), host.deploy.* and host.domain.verified (4), media.vod.*, media.clip.*, media.object.uploaded, media.storage.* (7), network.app.*, network.credential.*, network.grant.changed (5) and openre.* (7).

if (env.payload.redacted === true) return;                               // a tombstone (events.tombstone-payload@1)
const r = contracts.validate(`${env.event_type}@${env.version}`, env.payload);

Redaction (ADR-026). Any event may carry payload.redacts (events.redaction-directive@1), shaped { event_ids?, subject_type?, subject_ids? } with up to 1000 ids each, to take back the producer's own earlier events. OpenVibe.Events rewrites each target into a tombstone at its original seq: { redacted: true, redacted_at, redacted_by }, with the producer itself as actor. Naming another source's event refuses the whole batch with 403 events.redaction_not_allowed. A malformed directive gets 422 events.invalid_redaction. chat.message.deleted always carries one. Browsers are replayed only the public events of the last REALTIME_PUBLIC_REPLAY_SECONDS (default 300); an older cursor gets event: gap with reason public_window.

Project usage rollups (v0.63)

The services that own a developer capability count each project's use of it and report one rollup per closed hour, never one event per request (roadmap WS-N task 4). Every <service>.usage.recorded event carries common.usage-recorded@1: project, environment, capability, optional dimension (job type, tool), unit, window, quantity, errors, errors by code and up to ten sampled failures with their trace ids. Emitted today: tools.usage.recorded (jobs) and events.usage.recorded (published events, webhook deliveries). OpenVibe.Network adds them up per project and day and answers GET /api/v1/projects/:project/usage (network.project-usage-result@1) to the project's owner and admins; OpenVibe.Codes shows it as the project's usage page. A rollup is totals, not a delta: the latest one for a key replaces the earlier, so a producer may re-send a window safely.

Event delivery signatures

OpenVibe.Events POSTs each webhook delivery as { "event": <events.event-envelope@1>, "seq": n }, keyed with the subscription secret:

Header Value
X-OpenVibe-Signature (v1) sha256=<hex HMAC-SHA256 of the raw body>
X-OpenVibe-Timestamp <unix seconds> when this attempt was sent (every retry gets a fresh one)
X-OpenVibe-Signature-V2 t=<that timestamp>,v2=<hex HMAC-SHA256 of "<t>.<raw body>">

v1 covers only the body, so a captured delivery verifies forever (only event_id dedupe limits a replay). A consumer checking v2 refuses a timestamp more than 300 s from its own clock in either direction, compares in constant time, and never falls back to v1 when a v2 header is present but wrong or stale. openvibe-sdk ≥ 0.4.0 does this in parseDelivery(raw, headers, secret, { requireV2 }) and verifyDeliveryV2(); the openvibe-events package has verifyDeliveryV2().

Rollout: (1) Events sends v2 beside v1; (2) each consumer moves to SDK 0.4.0, which verifies v2 whenever it is present; (3) each consumer sets requireV2: true, so a v1-only (header-stripped) replay fails; (4) once every consumer requires v2, v1 may be dropped. See ADR-004.

Channel/owner lineage (v0.32)

One resolver answers "which channel, and whose, is this?" for channel, stream, VOD, clip, Pulse and creator-UI callers (roadmap §15.10, D20). OpenVibe.Live implements it at GET|POST /internal/lineage/resolve (capability live.lineage.resolve, loopback). The request is lineage.resolve-request@1, the answer lineage.resolution@1.

Precedence Input Rule Confidence
1 slug explicit_slug exact
2 slug <channel>/<slot>, parent_slug nested_slug exact
3 channel_id channel_id exact
4 stream_id, slot_id stream_lookup exact
5 clip_id, vod_id the record's lineage: its stream, its parent VOD (vod_parent), the owner recorded on it (legacy_metadata) derived
6 media_object_id media_lineage (legacy_map for a legacy: reference) derived / legacy_map
7 owner_subject owner_subject exact
8 legacy_ids legacy_map legacy_map

The first input that resolves decides. Every other input that resolves must name the same channel, or the answer is unresolved with reason conflict. A clip belongs to the clipped channel, never to the clipper. A display name never resolves anything: a request holding only one is answered unresolved, display_name_only.

Staff capability map (v0.34)

Staff roles and what each may do are one contract, policy.staff-role-map@1, and one map, manifests/policy/staff-roles.json (ADR-022). OpenVibe.Network owns the roles and issues the capabilities as claims in its user tokens, and every service checks a staff.* capability instead of comparing role names:

contracts.staff.can(req.user, 'staff.moderation.bans');   // claims { role, is_owner, staff_caps? }
contracts.staff.capabilitiesOf('global_mod');             // what a role holds
  • Roles, lowest first: user < streamer < global_mod < admin < owner. A role holds everything below it. user and streamer hold no staff capability.
  • owner is not a stored role. Tokens carry role: admin plus is_owner: true, and a role claim of owner counts as user.
  • Secrets, money, loyalty grants and making admins are the owner's alone.
  • Until Network issues staff_caps, can() derives them from the role, so the answer is the same before and after.
  • Channel powers stay with the product and are not staff capabilities: channel owner, channel moderators, stream owner and call creator are listed under local.
  • Each capability lists the role checks it replaces (gates, per service, file:line), so adoption in Live, Chat and Community is a mechanical swap.
  • Staff capabilities never appear in service, app or mod tokens, and are never delegated (ADR-003 amendment).

Tools platform API (v0.33)

Every tool on openvibe.tools gets a descriptor, tools.tool@1 (ADR-027). The page, the run API, openvibe-sdk and the docs all read it. It says:

  • where the tool runs (execution: client | sync | job);
  • whether the API exposes it (api), and how to call it (run);
  • its input schema, files, output, limits and auth;
  • its quota class and cost;
  • whether it fetches hosts the caller chose (egress);
  • optionally (v0.33.1), its catalogue keywords, which ?q= searches, and examples of runs for the docs and the OpenAPI document.
Route (openvibe.tools) Capability Contract
GET /api/v1/tools, /api/v1/tools/:id, /api/v1/tools/:id/schema tools.tool.read (public) tools.tool-list@1, tools.tool@1
POST /api/v1/tools/:id/run tools.tool.run (public); probes need tools.net.probe (partner) tools.run-request@1 → tools.run@1
/api/v1/jobs… (img, audio, docs; the gateway later) tools.job.create, .read, .cancel tools.job-request@1 → tools.job@1

A run is answered inline, or with its job (202 + Location) while a job tool is still working. Callers are tiered: anonymous < session < user < app/service. Quotas count each tool's cost within its quotaClass. yt is page-only (api: false). Only real tools have descriptors: there is no planned status, and a planned or mirror id is 404 tools.tool.not_found. The registry routes are served on the gateway and every satellite (tools.tool.read is active since v0.33.1), and so is the run route (tools.tool.run and tools.net.probe are active).

contracts.tools.checkDescriptor(d) and checkList(list) apply the rules that JSON Schema cannot express, such as run.path being the tool's own, and check each example's input against an embedded input schema. jobInput(d, input) builds a job tool's job input.

contracts.zones.checkObjectZone(z), checkObjectZoneList(list) and checkObjectZoneUsage(u) add the cross-field rules of the planned object zones (ADR-031 amendment 2026-10-02): ovrn and media_namespace name the zone's own project and id, a name belongs to one zone of its environment for ever, and a usage answer's ovrn names its zone_id. contracts.usage.checkUsageRecorded(u) refuses a usage rollup whose resource names another project than its project_id.

contracts.resources holds the resource names and the control-operation contract of ADR-048 (plan T13): parse(ovrn) and format({ service, project_id, type, id }) are the one OVRN parser and formatter, nameOf(summary) composes the name of a common.resource-summary@1, checkControlRequest(q) refuses a request whose resource names another project than its project_id (and create names resource_kind, every other action a resource), and checkControlResult(r) holds problem and confirmation_required to the result's state.

Tools' problem codes are listed with the routes that answer them (tools.run-request@1, tools.job-request@1, tools.tool-list@1, tools.tool@1). They include 400 tools.query.invalid, 404 tools.tool.not_found, 413 tools.pdf.too_many_pages, 422 tools.pdf.wrong_password, 503 tools.unavailable (a program or upstream a tool needs is missing), and, with Retry-After, 429 tools.quota.exceeded and 503 tools.busy.

Lifecycle declarations (v0.55)

Each service manifest has a lifecycle block, filled from the service's code and systemd units (roadmap WS-P task 1). A part that does not apply is { "none": "<why>" }.

Field What it says
liveness endpoint (the manifest's health) and means: what a 200 proves. Readiness stays ready.
shutdown signal, deadlineSeconds (signal to exit, as the process's own forced-exit timer bounds it), drains in order, optional workers (worker units that drain on their own) and note
startupRecovery resumes: what the next start picks up, each { kind: outbox|jobs|sessions|consumer|schedule|state, what }
rollback conditions (automatic and manual), window, blockers (forward-only migrations, table rebuilds, authority switches)
contracts range: the openvibe-contracts versions accepted, equal to contractRanges['openvibe-contracts']
leases claims: { what, holder, expires, fencing } for each thing claimed so that one owner acts at a time

ovhost validate <service> (OpenVibe.Host) reads the block from the inventory or from this package's manifest. It fails when a field is missing, when deadlineSeconds exceeds the unit's TimeoutStopSec or the unit's KillSignal is a different signal, and when the checkout's installed openvibe-contracts is outside contracts.range.

Run job API (v0.90)

OpenVibe.Run (manifests/services/run.json; plan T14 R1, deployed since 2026-10-07 and public at https://openvibe.run) takes jobs, places each on a node that advertises worker:<class> and sends it as platform.job@1. Its routes, under https://openvibe.run/api/v1:

Route Capability Body Answer
POST /api/v1/jobs run.job.submit run.job-create-request@1 run.job-create-result@1 (201, or 200 for an idempotent repeat)
GET /api/v1/jobs run.job.list run.job-list-query@1 (query string) run.job-list-result@1 (cursor pages)
GET /api/v1/jobs/:id run.job.read — run.job-read-result@1
POST /api/v1/jobs/:id/cancel run.job.cancel — run.job-read-result@1
POST /api/v1/jobs/:id/stream/ticket run.job.stream — run.job-stream-ticket-result@1 (one-use, 120 s)
GET /api/v1/jobs/:id/stream?ticket= run.job.stream — SSE of run.job-stream-event@1 (output, state, end; Last-Event-ID resumes)
GET /api/v1/admin/jobs run.job.admin (internal) run.job-list-query@1 with project_id run.job-list-result@1

A job moves queued → placed → running and ends succeeded, failed, cancelled or expired; Run publishes run.job.queued|started|succeeded|failed|cancelled|expired. Run mints the job id (job_<ULID>, ids.newId('job')) and passes it unchanged as the platform.job@1 id, so every metering reading is run:<job id>:<n> (service run, operation function.invoke, unit s) and usage.seconds is their sum. Inputs are Media objects pinned by sha256 (never a URL); egress takes Node's worker.egress values (none by default, public, openvibe-only).

Versioning and compatibility

  • A contract id is permanent. Minor versions only add optional fields.
  • A breaking change is a new file (<name>.v2.json) and catalog entry. The old one is listed in compatibility/deprecations.json with a replacement and a removeAfter date.
  • scripts/compat.js runs in npm test and CI. It fails on removed or retyped properties, newly required fields, removed enum values, tightened additionalProperties, or catalog removals without a deprecation, compared against the previous release tag.
  • generated/ is checked in; npm test fails if it is stale (npm run generate).
  • Every contract ships valid and invalid fixtures under fixtures/<id>/.

Purpose

The canonical, versioned source of every cross-service contract: subject identity, error envelope, service manifests, capability schemas, event envelopes and topics, user-module schemas, media/chat/community/billing/AI/games/jobs/release schemas, plus generated TypeScript types, JSON Schema and OpenAPI fragments. Everything else in the network migrates contract-by-contract instead of through copied prose.

Owns

  • contract IDs, semantic versions, owners, visibility and deprecation state
  • SubjectRef (user|guest|service|app|mod|system + stable id) and the legacy identity map shape
  • request/error envelope: traceparent, X-OpenVibe-Request-Id, Idempotency-Key, RFC 9457 problem details, resource revision/ETag
  • capability contract (id, owner, input/output schema, permissions, resource constraints, quota class, events)
  • service manifests, namespace and topic manifests, compatibility aliases and deprecations

Does not own

  • any runtime process, database or hosted API (the registry API lives in OpenVibe.Network)
  • product business logic

Planned surfaces

  • contracts/<domain>/… schemas with fixtures and examples
  • manifests/{services,namespaces,capabilities,topics}
  • generated/{typescript,json-schema,openapi} published as versioned packages
  • compatibility/aliases.json, compatibility/deprecations.json

Data (authority tables / families)

  • none (artifact repository)

Capabilities and events

Contracts implements no capability and produces or consumes no event (its service manifest lists none). It defines them: every capability manifest in manifests/capabilities/ names its owner, and capabilities.check() / serviceAuth.requireCapability() are how the other services enforce them. It calls no service. openvibe-contracts-check is the contract validation CI every service runs.

Depends on

  • OpenVibe.Network (issues subject IDs and service principals)

Acceptance (must be true before "done")

  • every contract carries id, version, owner, schema, visibility, compatibility policy, deprecation state, examples and generated types
  • a deprecated contract advertises its replacement and compatibility window
  • N/N-1 compatibility fixtures pass across core services

Bootstrap / extraction source

Derived from the implicit contracts already in the eleven current repositories (workspace CONTRACTS.md, Network prose contracts, Media/Live/Community route shapes). First deliverables: subject, errors, service manifest, capability schema, event envelope, media object IDs, service-token claims.

Security

Reporting a vulnerability: SECURITY.md. Contracts has no runtime, but its code runs in every service, so:

  • serviceAuth judges a request that presents a Bearer token on that token alone (a bad token is never rescued by a legacy header), verifies RS256 signatures, issuer, audience and expiry, and grants a capability only by exact id or a family.* grant (capabilities.check()).
  • The staff capability map (manifests/policy/staff-roles.json) is the one source for staff gates.
  • Schemas carry no secrets and no personal data; fixtures use fake values (gitleaks scans history in CI).
  • Dependencies are ajv and ajv-formats only.

Deploy

Nothing is deployed from this repository: there is no unit, port or env file. A release is a git tag (vX.Y.Z, after node scripts/compat.js <latest tag> passes); services pin the tag's tarball in package.json and pick it up with their next deploy (sudo ovhost deploy <service>). Network serves the registry (/api/v1/registry/services) from the tag it pins. A bad release is rolled back by consumers pinning the previous tag; a published tag is never moved.

Launch rule

This repository does not make a product real: a product domain stays an entry in manifests/products/<domain>.json (OpenVibe.Sites, which served the placeholder pages, retired on 2026-10-14) until all of the following exist in the product's own repository (plan §12.12):

  1. an owning runtime with health/readiness endpoints and observability;
  2. canonical identity/auth integration (OpenVibe.Network subjects, scoped service principals);
  3. server-rendered or static public routes that are useful without JavaScript;
  4. real persistence and end-to-end workflows;
  5. capability and event registration against OpenVibe.Contracts;
  6. a migration/seed strategy, a security/threat review, and sitemap/robots/feed behaviour;
  7. acceptance tests proving the advertised functionality.

The launch release moves the domain's catalog entry from manifests/products/<domain>.json to its service manifest's site (registry.service-manifest 1.1.0, without launch), switches routing and registers maturity in the ecosystem registry atomically. A placeholder is never counted as an implemented service.


Part of the OpenVibe network. Built in the open by OpenVibers.

Release note: v0.27.0 and v0.28.0

The v0.27.0 tag (commit 742db68) was cut on top of the 0.28.0 developer-app events commit, so its package reports version 0.28.0 and contains both changes (Codes: codes.app-manifest@1, codes.release.*; Events: events.app.*). v0.28.0 points at the same commit. Pin v0.28.0; v0.27.0 stays for consumers that already pinned it and is identical.