openvibers/openvibe.examples README.md

README.md

main 27.7 KB · 378 lines · markdown

1# OpenVibe.Examples
2
3> Executable public integration examples for the OpenVibe platform: the SDK, scoped credentials
4> and public APIs, nothing else.
5
6**Docs:** https://openvibe.services · **For AI:** https://openvibe.services/llms.txt
7**Install and test:** `npm ci && npm test` (Node 22 or newer)
8
9**Status:** alpha, 0.3.0 (roadmap Wave 20; the `create-openvibe-app` scaffolder, WS-N task 6). Eighteen examples, each with a smoke test that CI runs on
10Node 22.22.1 against `openvibe-sdk/testing`'s mock platform (no network). `npm run e2e` runs three
11of them (Media, Events pull, a Tools job) against the real platform with your own sandbox app; it
12is not part of CI or `npm test`, and it was not run against production as part of this release.
13`npm run developer-path` is the Wave 20 exit check (account to Media, Events and AI to revoked
14credentials); CI runs it against the mock platform, and OpenVibe.Host runs it against production daily.
15**Built on:** [openvibe-sdk v0.161.0](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.91.0), and
16openvibe-contracts v0.419.0 for the mod manifest.
17**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §15.1
18and §18.9; roadmap Wave 20, §30 (public SDK surface), ADR-014 (developer projects).
19**License:** MIT ([LICENSE](LICENSE)), so you can copy any example into your own project. The
20OpenVibe services themselves are AGPL-3.0.
21
22## Purpose
23
24Show, with code a developer can copy, how an app outside the network uses OpenVibe: only the public
25SDK, scoped developer-app credentials and public APIs, never a loopback key or a first-party database.
26Each example is small, runs on its own and proves one thing with a smoke test.
27
28## The examples
29
30| Example | What it proves | Uses |
31|---|---|---|
32| [browser-app](examples/browser-app) | A plain page (no build step) imports the SDK's browser bundle, reads the registry cross-origin and signs in with PKCE; a public app needs no secret | `openvibe-sdk/browser/openvibe-sdk.mjs`, `/auth` (`exchangeCode`, `verifyAppToken`) |
33| [node-server-app](examples/node-server-app) | Client-credentials app token per audience and what it carries, registry discovery, contracts version check, trace propagation | `openvibe-sdk/auth` (`getTokenInfo`), `/registry`, `/core` |
34| [media-uploader](examples/media-uploader) | Upload into, read from and delete in your project's Media tenant; sandbox files come back with signed URLs | `openvibe-sdk/media` |
35| [event-subscriber](examples/event-subscriber) | Publish to your project's topic `app.<project_key>.*`, pull it with a durable cursor, gaps; anonymous realtime for public events | `openvibe-sdk/events` (`createAppEvents`), `/realtime` |
36| [webhook-consumer](examples/webhook-consumer) | An app subscription on your own topic; `X-OpenVibe-Signature-V2` (±300 s replay window) verified on the raw body; exactly-once handling with an inbox; secret rotation | `openvibe-sdk/events` (`createAppEvents`, `parseDelivery`, `createPgInbox`) |
37| [tool-job](examples/tool-job) | An OpenVibe.Tools job submitted once (idempotency key), reattached after a restart, its SSE stream resumed with `Last-Event-ID` | `openvibe-sdk/jobs` |
38| [oauth-app](examples/oauth-app) | Authorization code + PKCE for a confidential app, exchange on the server, offline app-token verification, session hygiene | `openvibe-sdk/auth` |
39| [chat-bot](examples/chat-bot) | A bot on Chat's WebSocket protocol that stays in its one configured room and always identifies as a bot | Chat `/ws/chat` protocol (no SDK client: Chat is first-party) |
40| [mod-manifest](examples/mod-manifest) | A `mods.mod-manifest@1` validated against the schema, the capability catalog and compatibility ranges | `openvibe-contracts`, `openvibe-sdk/registry` |
41| [go-live](examples/go-live) | Go live on OpenVibe.Live from code: a Live API token finds or creates a WHIP slot and reads its WHIP URL, FFmpeg 8+ publishes (test pattern, a file or a camera), the session is named once media flows and ended when FFmpeg stops; the stream key is never printed | `openvibe-sdk/live` (`slots`, `streams`, `channels.url`), `/core`, FFmpeg's WHIP muxer |
42| [game-scores](examples/game-scores) | Achievements and a leaderboard for your game on OpenVibe.Play, from a sandbox app any developer can make today: register a `sandbox-` title, post a round (counted once on a retry), unlock, read the public board, delete the title | `openvibe-sdk/play` (`titles`, `scores`, `achievements`, `boards`) |
43| [inventory-wear](examples/inventory-wear) | Show what people wear on OpenVibe (hats, chat tags, badges, name effects) next to names in your own app: the public catalog and who wears what, 100 people per call, with no token; text and escaped HTML | `openvibe-sdk/inventory` (`kinds`, `definitions`, `equipped`) |
44| [space-deploy](examples/space-deploy) | Deploy a static site to OpenVibe.Space from CI with an app token (space.deploy.create, deployer role): the built folder as one deploy, live on main or a preview for a pull request, a refusal that changes nothing, rollback; a ready GitHub Actions workflow | `openvibe-sdk/space` (`deploys.uploadFiles`, `rollback`), `/auth` |
45| [zone-s3](examples/zone-s3) | OpenVibe.Zone's private buckets over S3 with the official AWS SDK v3: put, get, a paged list, a presigned share link (at most 7 days) and delete; path-style, us-east-1, SigV4, an access key scoped to the zone | `@aws-sdk/client-s3`, `@aws-sdk/s3-request-presigner` |
46| [ai-chat](examples/ai-chat) | The official `openai` npm client on `https://ai.openvibe.services/v1` with a Network app token: plain and streamed Chat Completions, `x_openvibe` run info, the project's own provider by secret name | `openai` (`baseURL`, `apiKey` as a token function), `openvibe-sdk/auth` |
47| [ai-anthropic](examples/ai-anthropic) | The official `@anthropic-ai/sdk` client on `https://ai.openvibe.services` (POST /v1/messages) with a Network app token through the SDK's `credentials`: the answer names the model that ran and carries `x_openvibe`. |
48| [vibes-pay](examples/vibes-pay) | Sell a sticker pack for Vibes: the server creates a charge with the SDK (the order id is the idempotency key), the page opens the pay sheet with `vibes-pay.js`, and a signed webhook marks the order paid once. | `openvibe-sdk/vibes` (`charges.create`, `webhooks.verify`), `openvibe-shared/vibes-pay.js` (`OpenVibe.vibes.pay`, `[data-ov-vibes-pay]`) |
49| [ai-python](examples/ai-python) | The official `openai` Python client on `https://ai.openvibe.services/v1`, with a Network app token from client credentials kept and renewed a minute early. |
50
51Each example folder has a README, a `.env.example`, its own `package.json` and a
52`test/smoke.test.js`. Configuration is environment variables only (`OV_CLIENT_ID`,
53`OV_CLIENT_SECRET`, `OV_PROJECT_ID`, …); secrets are never logged or sent to a browser.
54
55## Start a new app
56
57`create-openvibe-app` copies one of these examples into a new folder as your starting point, with
58its smoke test, a `.gitignore`, the package renamed and the SDK pinned to the same tag. It needs no
59network beyond fetching this package.
60
61```bash
62npx --package=https://codeload.github.com/OpenVibers/OpenVibe.Examples/tar.gz/refs/tags/v0.3.0 create-openvibe-app my-app --template web
63cd my-app && npm install && cp .env.example .env && npm test
64```
65
66| Template | Starts from | For |
67|---|---|---|
68| `web` | [browser-app](examples/browser-app) | a plain web page that signs in with OpenVibe (PKCE) and calls a public API |
69| `server` | [node-server-app](examples/node-server-app) | a Node server with a client-credentials app token and registry discovery |
70| `bot` | [chat-bot](examples/chat-bot) | a chat bot that stays in its one room and always identifies as a bot |
71| `mod` | [mod-manifest](examples/mod-manifest) | a mod manifest validated against the schema, capabilities and compatibility ranges |
72
73Without `--template` it asks on a terminal (and picks `web` otherwise). `npm create openvibe-app`
74will work the same way once the package is on the npm registry; until then use the `npx` line.
75
76## End-to-end walkthrough
77
78From a new account to the examples running against the platform, with public surfaces only.
79Nothing here needs staff: a new project is `sandbox`, and a sandbox app's owner gets the sandbox
80capabilities approved at once.
81
82**1. Create an account** at <https://openvibe.network>.
83
84**2. Create a project, a sandbox app and its grants.** Either:
85
86- **in the developer console, <https://openvibe.services>:** sign in, create a project, add a
87 **confidential** app in the `sandbox` environment with the redirect URI
88 `http://localhost:3009/callback` (for [oauth-app](examples/oauth-app)), copy the client secret
89 (it is shown **once**), and request the grants `media.object.upload`, `media.object.read`,
90 `events.app.publish`, `events.app.read`, `events.app.subscribe`, `tools.job.create`,
91 `tools.job.read`; or
92- **with the projects API** (`openvibe-sdk/projects`): `npm run new-app` does exactly the above
93 and writes `OV_CLIENT_ID`, `OV_CLIENT_SECRET` and `OV_PROJECT_ID` into `./.env` (mode 0600)
94 without printing the secret. It signs in with your username and password (not echoed) or uses
95 `OV_USER_TOKEN`. The same in code:
96
97 ```js
98 const { createClient } = require('openvibe-sdk/core');
99 const { createProjectsClient } = require('openvibe-sdk/projects');
100 const projects = createProjectsClient(createClient({ token: userAccessToken })); // a Network user token
101 const project = await projects.create({ name: 'My first OpenVibe app' }); // sandbox, you are the owner
102 const app = await projects.apps.create(project.id, { name: 'server', environment: 'sandbox', type: 'confidential' });
103 app.credential.client_secret; // shown ONCE: store it now
104 await projects.grants.request(project.id, app.id, 'media.object.upload'); // approved at once for the owner
105 ```
106
107For [browser-app](examples/browser-app) add a second, **public** app with the redirect URI
108`http://localhost:3001/callback` and `media.object.read` (`npm run new-app -- --public --project
109prj_…`). A public app has no secret.
110
111**3. Check it works end to end:**
112
113```bash
114npm ci
115npm run e2e # reads ./.env: OV_CLIENT_ID, OV_CLIENT_SECRET, OV_PROJECT_ID
116```
117
118It uploads a small file to your Media tenant, reads it back and deletes it; publishes
119`app.<project_key>.e2e.ran` and pulls your topic until it finds it; converts a 1x1 PNG to WebP on
120`openvibe.tools` (the gateway's jobs facade), following the job's events. It prints each step (never the secret, a token
121or a URL signature) and exits `1` if a step failed, `2` if a variable is missing.
122
123**4. Run the examples.** Each reads `.env` in its own folder (`cp .env.example .env`), or point it
124at the root one: `node --env-file=../../.env upload.js ./logo.png`.
125
126| Example | App | Grants | Environment variables |
127|---|---|---|---|
128| [node-server-app](examples/node-server-app) | confidential | any | `OV_CLIENT_ID`, `OV_CLIENT_SECRET`, `OV_AUDIENCES` |
129| [media-uploader](examples/media-uploader) | confidential | `media.object.upload`, `media.object.read` | `OV_CLIENT_ID`, `OV_CLIENT_SECRET`, optional `OV_PROJECT_ID` |
130| [event-subscriber](examples/event-subscriber) | confidential | `events.app.publish`, `events.app.read` | `OV_CLIENT_ID`, `OV_CLIENT_SECRET`, optional `OV_PROJECT_ID`, `OV_TOPICS`, `OV_PLATFORM_TOPICS`; realtime: `OV_EVENTS_MODE=realtime`, `OV_TOPICS` only |
131| [webhook-consumer](examples/webhook-consumer) | confidential | `events.app.subscribe` | `OV_CLIENT_ID`, `OV_CLIENT_SECRET`; the consumer: `OV_WEBHOOK_SECRET` (written by `subscribe.js`); a public https host |
132| [tool-job](examples/tool-job) | confidential | `tools.job.create`, `tools.job.read` | `OV_CLIENT_ID`, `OV_CLIENT_SECRET`, optional `OV_TOOLS_JOBS_URL`, `OV_JOB_TYPE`, `OV_JOB_INPUT` |
133| [oauth-app](examples/oauth-app) | confidential, redirect `http://localhost:3009/callback` | every capability in `OV_SCOPE` | `OV_CLIENT_ID`, `OV_CLIENT_SECRET`, `OV_AUDIENCE`, `OV_SCOPE` |
134| [browser-app](examples/browser-app) | public, redirect `http://localhost:3001/callback` | `media.object.read` | `OV_CLIENT_ID`, `OV_AUDIENCE`, `OV_SCOPE` |
135| [chat-bot](examples/chat-bot) | none: a Live API token (`hbt_…`) of a dedicated bot account | Live token scope `chat` | `OV_CHAT_TOKEN`, one of `OV_CHAT_CHANNEL_USER_ID` / `OV_CHAT_STREAM_ID` |
136| [mod-manifest](examples/mod-manifest) | none | none | `OV_NETWORK_URL` for `--registry` |
137| [ai-chat](examples/ai-chat) | confidential | `ai.app.run` | `OV_CLIENT_ID`, `OV_CLIENT_SECRET`, optional `OV_AI_URL`; own provider: `OV_PROVIDER_SECRET` (a secret's name), `OV_PROVIDER_BASE_URL`, `OV_PROVIDER_MODEL` |
138| [ai-anthropic](examples/ai-anthropic) | confidential | `ai.app.run` | `OV_CLIENT_ID`, `OV_CLIENT_SECRET`, optional `OV_MODEL`, `OV_NETWORK_URL`, `OV_AI_URL` |
139| [vibes-pay](examples/vibes-pay) | confidential | `billing.vibes.charge` | `OV_APP_TOKEN`, `OV_VIBES_WEBHOOK_SECRET`, `OV_BILLING_URL`, `OV_PUBLIC_URL`, `OV_ITEM_NAME`, `OV_PRICE_VIBES`, `OV_PORT` |
140| [ai-python](examples/ai-python) | confidential | `ai.app.run` | `OV_CLIENT_ID`, `OV_CLIENT_SECRET`, optional `OV_MODEL`, `OV_NETWORK_URL`, `OV_AI_URL` |
141
142Rotate a secret in the portal or with `projects.credentials.rotate()`, revoke the app with
143`projects.apps.revoke()`. Issued tokens end within their 5-minute lifetime.
144
145## How the platform works for a sandbox app
146
147Verified in production on 2026-09-23 with a brand-new account, using the public endpoints only:
148
149- **Projects.** A new project is `sandbox`. Its owner's grant requests for the sandbox allowance
150 are approved at once: `media.object.upload|read`, `events.app.publish|read|subscribe`,
151 `tools.job.create|read|cancel`.
152- **Tokens.** `POST https://openvibe.network/oauth/token` with `grant_type=client_credentials`
153 gives a confidential sandbox app a 5-minute token for `openvibe.media`, `openvibe.events` or
154 `openvibe.tools`, with `project_id`, `env: sandbox`, `ns: [project_id]` and the approved
155 capabilities for that audience.
156- **Media.** The tenant is the project id: `/api/v1/<prj_…>/files`. Sandbox objects are stored in
157 a separate sandbox tenant (`<prj_…>-sandbox`, not addressable by path) and never served
158 publicly: signed URLs only.
159- **Events.** App events are `app.<project_key>.<name…>` (`project_key` = `p` + the lowercased
160 project ULID) with `source: app-<lowercased app ULID>`. Read them with
161 `GET /api/v1/events?topic=app.<project_key>.*`; subscribe with public https endpoints only.
162 Realtime (SSE) never streams app events.
163- **Tools.** `/api/v1/jobs` accepts sandbox app tokens.
164- **Discovery.** `/.well-known/openvibe` and `/api/v1/registry/*` answer any origin (CORS), so a
165 browser page reads them directly.
166
167## Developer path check
168
169`scripts/developer-path.js` is the roadmap Wave 20 exit check as a repeatable script: a developer
170goes from an account to a working Media, Events and AI integration with public endpoints, the SDK and
171scoped credentials only, then rotates and revokes those credentials and cleans up. OpenVibe.Host runs it
172against production every day (`openvibe-devpath.timer`, shown on openvibe.network/status).
173
174| Step | What it does |
175|---|---|
176| 1. account | registers `OV_E2E_USERNAME` (`--register`) or signs in at `/api/auth/*`, or uses `OV_USER_TOKEN` |
177| 2. discovery | reads `/.well-known/openvibe` as any origin may |
178| 3. project | creates a sandbox project with `openvibe-sdk/projects` |
179| 4. app | creates a confidential sandbox app; its secret stays in memory |
180| 5. grants | requests `media.object.upload`, `media.object.read`, `events.app.publish`, `events.app.read`, `ai.app.run`, `play.title.manage`, `play.result.write`, `services.release.manage`; all must be approved at once |
181| 6. media | [media-uploader](examples/media-uploader): upload a small file, read it back, delete it |
182| 7. events | [event-subscriber](examples/event-subscriber): publish `app.<project_key>.developer_path.ran`, pull it with a cursor |
183| 8. ai | [ai-chat](examples/ai-chat): the official `openai` client on OpenVibe.AI's OpenAI-compatible endpoint (`OV_AI_URL`) with an app token for `openvibe.ai`; one short answer, which must carry its `x_openvibe` run id |
184| 9. play | [game-scores](examples/game-scores): a `sandbox-` title on OpenVibe.Play (`OV_PLAY_URL`), a round posted twice and recorded once, an unlock, the public board read without a token, the title deleted |
185| 10. export | as the project's owner: Network mints 5-minute read-only export tokens; Media lists the project's sandbox files with one and refuses an upload; Events returns the event from step 7 with the other |
186| 11. release | as the app, on OpenVibe.Services (`OV_SERVICES_URL`): a sandbox release of itself goes draft → published → deprecated → revoked, checked in the public list each time |
187| 12. credentials | rotates with no overlap and revokes the first credential; the old secret must be refused at `/oauth/token`, the new one must work |
188| 13. cleanup | archives the project, even when an earlier step failed; the app's secret must then be refused |
189
190**In CI**, on every push, `npm run developer-path:mock` runs the same code against
191`openvibe-sdk/testing`'s mock platform (the `developer-path` job; `test/developer-path.test.js`
192also checks it leaves nothing behind, never prints a secret, and still cleans up after a failure).
193The mock has no account API, so `scripts/developer-path-mock.js` adds register and login routes
194that mint mock user tokens, mints export tokens, and stands in for Services' release API, AI's
195`/v1/chat/completions` and Play's title, score, unlock and board routes (with its sandbox rules); it hands the mock the contract's public capabilities, as Network reads them. Every
196other step is the platform mock's own.
197
198**Against production** it runs only when a person starts it. Credentials come from the
199environment (or `./.env`) only:
200
201```bash
202OV_E2E_USERNAME=… OV_E2E_PASSWORD=… npm run developer-path # an existing account
203OV_E2E_USERNAME=… OV_E2E_PASSWORD=… npm run developer-path -- --register # create that account first
204OV_USER_TOKEN=… npm run developer-path # a Network user access token
205```
206
207It creates one project per run and archives it at the end (Network keeps archived projects).
208It refuses to start without credentials, and refuses in CI against the production Network (`CI`
209set and `OV_NETWORK_URL` unset or `https://openvibe.network`), so it can later run in CI against
210an integration environment but never against production by accident. It masks every password,
211secret and token in its output and prints signed URLs without their signature. Exit `0` when all
212thirteen steps passed, `1` when one failed, `2` when it refused to start.
213
214It does not yet cover webhook delivery to an external endpoint (that needs a public https host)
215or publishing an app release in OpenVibe.Services.
216
217### Tools job proof
218
219`scripts/tools-job-proof.js` (`npm run tools-job-proof`) is the scheduled end-to-end proof of
220OpenVibe.Tools jobs (roadmap WS-L task 4), again with the SDK only. A **service principal** runs it,
221because sandbox jobs are never announced to Events or copied to Media:
222
2231. **token**: client credentials for `tools.job.create` and `tools.job.read` (`openvibe.tools`) and
224 `events.event.read` (`openvibe.events`).
2252. **cursor**: the head of the Events store for `tools.job.*`, before anything is submitted.
2263. **submit**: `img.process` converts a small generated PNG to WebP.
2274. **reattach**: the progress stream is dropped after its first event and reattached with
228 `Last-Event-ID`, as the Tools UI does after a reload. Every later event must arrive once, in
229 order, up to `job.succeeded`.
2305. **result**: the job reads back `succeeded`, and its file is stored in Media (`storage: "media"`
231 with a media id). The download must be a WebP that matches the listed sha256. Tools keeps no
232 local copy of a Media-stored result.
2336. **events**: `tools.job.created`, `tools.job.started` and `tools.job.succeeded` for the job are
234 in the Events store.
235
236```bash
237OV_CLIENT_ID=… OV_CLIENT_SECRET=… npm run tools-job-proof [-- --result last.json]
238```
239
240`OV_OAUTH_CLIENT_ID` and `OV_OAUTH_CLIENT_SECRET` also work; Network's service-principal setup
241writes those names. OpenVibe.Host runs it every six hours as the `probe` principal
242(`openvibe-toolsjob.timer`). CI runs it against the mock platform (`test/tools-job-proof.test.js`),
243and it refuses to run in CI against production.
244
245## What still does not work
246
247- **No refresh tokens, and no profile.** Network's consent screen lists the capabilities an app asks for, and a
248 person who continues gives it a 5-minute app token with no refresh token. The app learns only the person's `usr_…`
249 id (`on_behalf_of`): profile lookups (`identity.subject.resolve`) are `first-party`.
250- **`/oauth/token` is not CORS-open**, like every Network route outside discovery and the
251 registry, so a browser app exchanges its code on its own server
252 ([browser-app](examples/browser-app) does).
253- **Chat has no app principal.** `chat.message.send` is `first-party`; the
254 [chat-bot](examples/chat-bot) signs in with a Live API token (`hbt_…`, scope `chat`) of a
255 dedicated bot account, not with a developer app.
256- **Production apps need staff**: switching a project to `sandbox+production` and setting its
257 allowance.
258- **Publishing a mod** into Games is not public (`games.mod.manage` is `first-party`).
259
260## Running the tests
261
262```bash
263fnm use 22.22.1 # or any Node 22
264npm ci # one install for all examples (npm workspaces)
265npm test # every smoke test + the repository checks
266npm test -- chat oauth # only some
267cd examples/tool-job && npm test
268```
269
270How the tests stay offline:
271
272- Network, Events, Media and Tools jobs are `createMockPlatform()` from `openvibe-sdk/testing`: an
273 in-process fake at the real public origins, with real RS256 tokens, developer apps and projects,
274 sandbox handling per service, `/oauth/authorize` with auto-consent, retention gaps
275 (`pruneEvents()`), a delivery worker (`deliverEvents()`) and Tools jobs with SSE.
276- The mock plays the developer-app rules the way production does: Events' `events.app.*`
277 (own-project topics, `app-<ULID>` sources, env separation), Media's project tenants (`prj_…` and
278 `prj_…-sandbox`, signed URLs for sandbox files) and Tools at its satellites' origins.
279- Chat is the only service the SDK does not mock: chat-bot carries `test/mock-chat-server.js`.
280- Every smoke test replaces the global `fetch` with one that throws, so an accidental real network
281 call fails the test.
282
283Repository checks (`test/`):
284
285- `public-surface.test.js` fails on the loopback shared-key header, on the platform's internal
286 route prefix, on hard-coded loopback service ports outside the local mocks, and on any secret
287 value in a `.env.example`.
288- `structure.test.js` checks the ten examples are complete and copyable: README with "What it
289 proves" and "Run it against the real platform", a `.env.example` that names every variable the
290 code reads, MIT license, `openvibe-sdk` pinned to one tag (the root package.json's), no `file:` links; and that
291 `npm run e2e` is outside CI and `npm test` and refuses to start without its variables; and that
292 CI runs the developer path against the mock platform only.
293- `developer-path.test.js` runs the developer path against the mock platform and checks the
294 result (see [Developer path check](#developer-path-check)).
295
296## Layout
297
298```
299examples/<name>/ one example: README.md, .env.example, package.json, code, test/smoke.test.js
300scripts/e2e.js npm run e2e: three examples against the real platform (on demand only)
301scripts/developer-path.js npm run developer-path: the Wave 20 exit check against the real platform (on demand only)
302scripts/developer-path-mock.js npm run developer-path:mock: the same flow against the mock platform (CI)
303scripts/new-sandbox-app.js npm run new-app: project + sandbox app + grants via openvibe-sdk/projects
304test/run.js runs every smoke test and the repository checks (npm test)
305test/public-surface.test.js, test/structure.test.js
306.github/workflows/ci.yml Node 22.22.1: npm test; the developer path against the mock platform
307```
308
309## Owns
310
311- the nine charter examples: vanilla browser app, Node server app, webhook consumer, event
312 subscriber, Media uploader, Chat bot, tool/job example, mod manifest example, OAuth app example; plus the AI chat example (the openai client on AI's OpenAI-compatible endpoint) the go-live example (Live over WHIP from code), the zone-s3 example (Zone with the AWS SDK v3), the space-deploy example (Space deploys from CI) and the inventory-wear example (what people wear, rendered in your app)
313- the on-demand end-to-end run (`npm run e2e`) and the sandbox-app setup script
314- the developer path check (`npm run developer-path`, and `developer-path:mock` in CI)
315
316## Does not own
317
318- any platform service, the SDK (OpenVibe.SDK), the contracts (OpenVibe.Contracts) or the
319 developer console (OpenVibe.Services)
320
321## Depends on
322
323- OpenVibe.SDK (v0.65.0), OpenVibe.Contracts (v0.214.0, mod manifest only)
324- at run time against the real platform: Network (tokens, registry, developer projects), Events,
325 Media, Tools, Chat (on openvibe.live)
326
327## Capabilities
328
329Examples implements no capability (its manifest lists none). The examples call the platform as a
330developer app with the grants a sandbox project asks for (`npm run new-app`): `media.object.upload`
331and `media.object.read` (Media), `events.app.publish`, `events.app.read` and `events.app.subscribe`
332(Events), and `tools.job.create`, `tools.job.read` and `tools.job.cancel` (Tools); a public browser app
333gets `media.object.read` only. The developer path check also uses `ai.app.run` (AI) and `services.release.manage` (Services). The
334chat bot uses a Live `hbt_` token with scope `chat`, because `chat.message.send` is first-party.
335
336## Acceptance
337
338- CI runs every example: **yes**, against the SDK's mock platform (and a small Chat WebSocket
339 mock for chat-bot; no network), and the developer path check against the same mock. Against
340 the real platform on demand: `npm run e2e` with a sandbox app's credentials and
341 `npm run developer-path` with an account; there is no integration environment with
342 credentials for CI.
343- A new developer goes from account creation to a working Media, event and capability
344 integration using only public documentation, the SDK and scoped credentials: the platform path
345 was verified in production on 2026-09-23 (see
346 [How the platform works](#how-the-platform-works-for-a-sandbox-app)) by a curl script against the
347 public endpoints, not by the SDK or these examples. That check is now committed as
348 `npm run developer-path` (SDK and examples, plus credential rotation and revocation), runs in CI
349 against the mock platform, and has not yet been run against production. `npm run e2e` walks
350 three of the nine examples along the same path. **Partly met.**
351- No example needs a loopback-only internal key or first-party database access: **yes**, enforced
352 by `test/public-surface.test.js`.
353
354## Security
355
356Reporting a vulnerability: [SECURITY.md](SECURITY.md). Configuration is environment variables only;
357secrets are never logged or sent to a browser, and `.env.example` files hold names, not values
358(`test/public-surface.test.js` fails on a secret value there, on the loopback shared-key header and on
359internal routes or ports). Every smoke test replaces `fetch` with one that throws, so no test reaches the
360network. `npm run developer-path` refuses to run against production in CI.
361
362## Deploy
363
364Nothing is deployed from this repository: it has no unit, port or env file and is not published as a
365package. CI (`.github/workflows/ci.yml`) runs `npm test` and the developer path against the mock
366platform on every push. Copy an example, or start one with `create-openvibe-app`, and deploy it as your
367own app.
368
369---
370
371Part of the [OpenVibe network](https://openvibe.network). Built in the open by [OpenVibers](https://github.com/OpenVibers).
372
373<!-- versions:start -->
374- openvibe-contracts: v0.419.0
375- openvibe-sdk: v0.161.0
376- openvibe-shared: v4.237.0
377<!-- versions:end -->
378