openvibers/openvibe.examples Commits
Examples: the developer path releases on OpenVibe.Services (services.release.manage, OV_SERVICES_URL); openvibe-sdk v0.35.2 (#4)
Changes
@@@ -9,7 +9,7 @@ of them (Media, Events pull, a Tools job) against the real platform with your owis not part of CI or `npm test`, and it was not run against production as part of this release.`npm run developer-path` is the Wave 20 exit check (account to Media and Events to revokedcredentials); CI runs it against the mock platform, and a person runs it against production.--**Built on:** [openvibe-sdk v0.35.0](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.35.0), and++**Built on:** [openvibe-sdk v0.35.2](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.35.2), andopenvibe-contracts v0.112.0 for the mod manifest.**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §15.1and §18.9; roadmap Wave 20, §30 (public SDK surface), ADR-014 (developer projects).@@@ -71,7 +71,7 @@ capabilities approved at once.**2. Create a project, a sandbox app and its grants.** Either:--- **in the developer portal, <https://openvibe.codes>:** sign in, create a project, add a++- **in the developer console, <https://openvibe.services>:** sign in, create a project, add a**confidential** app in the `sandbox` environment with the redirect URI`http://localhost:3009/callback` (for [oauth-app](examples/oauth-app)), copy the client secret(it is shown **once**), and request the grants `media.object.upload`, `media.object.read`,@@@ -160,11 +160,11 @@ scoped credentials only, then rotates and revokes those credentials and cleans u| 2. discovery | reads `/.well-known/openvibe` as any origin may || 3. project | creates a sandbox project with `openvibe-sdk/projects` || 4. app | creates a confidential sandbox app; its secret stays in memory |--| 5. grants | requests `media.object.upload`, `media.object.read`, `events.app.publish`, `events.app.read`, `codes.release.manage`; all must be approved at once |++| 5. grants | requests `media.object.upload`, `media.object.read`, `events.app.publish`, `events.app.read`, `services.release.manage`; all must be approved at once || 6. media | [media-uploader](examples/media-uploader): upload a small file, read it back, delete it || 7. events | [event-subscriber](examples/event-subscriber): publish `app.<project_key>.developer_path.ran`, pull it with a cursor || 8. 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 |--| 9. release | as the app, on OpenVibe.Codes (`OV_CODES_URL`): a sandbox release of itself goes draft → published → deprecated → revoked, checked in the public list each time |++| 9. 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 || 10. credentials | rotates with no overlap and revokes the first credential; the old secret must be refused at `/oauth/token`, the new one must work || 11. cleanup | archives the project, even when an earlier step failed; the app's secret must then be refused |@@@ -172,7 +172,7 @@ scoped credentials only, then rotates and revokes those credentials and cleans u`openvibe-sdk/testing`'s mock platform (the `developer-path` job; `test/developer-path.test.js`also checks it leaves nothing behind, never prints a secret, and still cleans up after a failure).The mock has no account API, so `scripts/developer-path-mock.js` adds register and login routes--that mint mock user tokens, mints export tokens, and stands in for Codes' release API; every other step is the platform mock's own.++that mint mock user tokens, mints export tokens, and stands in for Services' release API; every other step is the platform mock's own.**Against production** it runs only when a person starts it. Credentials come from theenvironment (or `./.env`) only:@@@ -191,7 +191,7 @@ secret and token in its output and prints signed URLs without their signature. Eeleven steps passed, `1` when one failed, `2` when it refused to start.It does not yet cover webhook delivery to an external endpoint (that needs a public https host)--or publishing an app release in OpenVibe.Codes.++or publishing an app release in OpenVibe.Services.### Tools job proof@@@ -265,7 +265,7 @@ Repository checks (`test/`):value in a `.env.example`.- `structure.test.js` checks the nine examples are complete and copyable: README with "What itproves" and "Run it against the real platform", a `.env.example` that names every variable the-- code reads, MIT license, `openvibe-sdk` pinned to the v0.35.0 tag, no `file:` links; and that++ code reads, MIT license, `openvibe-sdk` pinned to the v0.35.2 tag, no `file:` links; and that`npm run e2e` is outside CI and `npm test` and refuses to start without its variables; and thatCI runs the developer path against the mock platform only.- `developer-path.test.js` runs the developer path against the mock platform and checks the@@@ -294,11 +294,11 @@ test/public-surface.test.js, test/structure.test.js## Does not own- any platform service, the SDK (OpenVibe.SDK), the contracts (OpenVibe.Contracts) or the-- developer portal (OpenVibe.Codes)++ developer console (OpenVibe.Services)## Depends on--- OpenVibe.SDK (v0.35.0), OpenVibe.Contracts (v0.112.0, mod manifest only)++- OpenVibe.SDK (v0.35.2), OpenVibe.Contracts (v0.113.0, mod manifest only)- at run time against the real platform: Network (tokens, registry, developer projects), Events,Media, Tools, Chat (on openvibe.live)@@@ -308,7 +308,7 @@ Examples implements no capability (its manifest lists none). The examples call tdeveloper app with the grants a sandbox project asks for (`npm run new-app`): `media.object.upload`and `media.object.read` (Media), `events.app.publish`, `events.app.read` and `events.app.subscribe`(Events), and `tools.job.create`, `tools.job.read` and `tools.job.cancel` (Tools); a public browser app--gets `media.object.read` only. The developer path check also uses `codes.release.manage` (Codes). The++gets `media.object.read` only. The developer path check also uses `services.release.manage` (Services). Thechat bot uses a Live `hbt_` token with scope `chat`, because `chat.message.send` is first-party.## Acceptance@@@ -349,5 +349,5 @@ own app.Part of the [OpenVibe network](https://openvibe.network). Built in the open by [OpenVibers](https://github.com/OpenVibers).<!-- versions:start -->--- openvibe-sdk: v0.35.0++- openvibe-sdk: v0.35.2<!-- versions:end -->@@@ -4,7 +4,7 @@"code": true,"wave": "W20","version": "0.3.0",-- "sdk": "openvibe-sdk v0.35.0",++ "sdk": "openvibe-sdk v0.35.2","contracts": "openvibe-contracts v0.49.0 (mod-manifest example)","deployed": false,"deployment": "none: a repository with CI, not a runtime and not a published package",@@@ -13,6 +13,6 @@"node": ">=22"},"dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"}}@@@ -13,6 +13,6 @@"node": ">=22"},"dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"}}@@@ -13,6 +13,6 @@"node": ">=22"},"dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"}}@@@ -13,7 +13,7 @@"node": ">=22"},"dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0",++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.112.0"}}@@@ -13,6 +13,6 @@"node": ">=22"},"dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"}}@@@ -13,6 +13,6 @@"node": ">=22"},"dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"}}@@@ -13,6 +13,6 @@"node": ">=22"},"dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"}}@@@ -13,7 +13,7 @@"node": ">=22"},"dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0",++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2","better-sqlite3": "^11.10.0"}}@@@ -15,7 +15,7 @@"create-openvibe-app": "scripts/create-openvibe-app.js"},"devDependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"},"engines": {"node": ">=22"@@@ -26,7 +26,7 @@"version": "0.2.0","license": "MIT","dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"},"engines": {"node": ">=22"@@@ -48,7 +48,7 @@"version": "0.2.0","license": "MIT","dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"},"engines": {"node": ">=22"@@@ -59,7 +59,7 @@"version": "0.2.0","license": "MIT","dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"},"engines": {"node": ">=22"@@@ -71,7 +71,7 @@"license": "MIT","dependencies": {"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.112.0",-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"},"engines": {"node": ">=22"@@@ -82,7 +82,7 @@"version": "0.2.0","license": "MIT","dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"},"engines": {"node": ">=22"@@@ -93,7 +93,7 @@"version": "0.2.0","license": "MIT","dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"},"engines": {"node": ">=22"@@@ -104,7 +104,7 @@"version": "0.2.0","license": "MIT","dependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"},"engines": {"node": ">=22"@@@ -116,7 +116,7 @@"license": "MIT","dependencies": {"better-sqlite3": "^11.10.0",-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"},"engines": {"node": ">=22"@@@ -472,9 +472,10 @@"link": true},"node_modules/openvibe-sdk": {-- "version": "0.35.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0",-- "integrity": "sha512-IF7XB97tU7Q+ccwssGx6QXXcfDPcVpl+mX3BjIr7C6NJlcmoqj9DbHiKrBxlVav0eG44Jf8zuXEwokrHDq3J0w==",++ "version": "0.35.2",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2",++ "integrity": "sha512-HudKZJK3lZ9i0+yRdMrz9pJmIrYNTN8AFqbHFCz5jDCF68CfZwUJQyhE61pHODzGRyTvUi4DtAzkd0WEzIWx/g==",++ "dev": true,"license": "MIT","engines": {"node": ">=20"@@@ -23,6 +23,6 @@"node": ">=22"},"devDependencies": {-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"}}@@@ -10,21 +10,21 @@* the flow uses, /api/auth/register and /api/auth/login, as a thin layer: register creates a mock* user and returns a Network user token for it; login returns one for an existing user (the mock* does not check passwords). It also mints Network's export tokens (the export step, signed by the-- * platform like any app token) and stands in for OpenVibe.Codes' release API (the release step):-- * an app token for openvibe.codes carrying codes.release.manage manages the app's own releases,-- * draft → published → deprecated → revoked, and the public list shows every release but drafts, like Codes.++ * platform like any app token) and stands in for OpenVibe.Services' release API (the release step):++ * an app token for openvibe.services carrying services.release.manage manages the app's own releases,++ * draft → published → deprecated → revoked, and the public list shows every release but drafts, like Services.* Everything else is the platform mock's own.*/const { createMockPlatform } = require('openvibe-sdk/testing');--const { runDeveloperPath, PRODUCTION_NETWORK, PRODUCTION_CODES } = require('./developer-path');++const { runDeveloperPath, PRODUCTION_NETWORK, PRODUCTION_SERVICES } = require('./developer-path');function withAccounts(platform, network = PRODUCTION_NETWORK) {const byName = new Map();const reply = (status, body) => new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } });-- // OpenVibe.Codes' release API, in memory (the token's signature is the platform's; here its claims decide).++ // OpenVibe.Services' release API, in memory (the token's signature is the platform's; here its claims decide).const releases = new Map();let n = 0;-- function codes(url, method, init) {++ function services(url, method, init) {const m = url.pathname.match(/^\/api\/v1\/(?:apps\/(app_[0-9A-Z]+)\/releases|releases\/([a-z0-9_]+)\/(publish|deprecate|revoke))$/i);if (!m) return reply(404, { code: 'route.not_found' });const list = (appId) => [...releases.values()].filter((r) => r.app_id === appId && r.status !== 'draft');@@@ -32,7 +32,7 @@ function withAccounts(platform, network = PRODUCTION_NETWORK) {let claims = null;try { claims = JSON.parse(Buffer.from(String((init.headers || {}).Authorization || '').replace(/^Bearer /, '').split('.')[1], 'base64url').toString('utf8')); } catch { claims = null; }if (!claims) return reply(401, { code: 'auth.required' });-- if (!(claims.aud || []).includes('openvibe.codes') || !(claims.cap || []).includes('codes.release.manage')) return reply(403, { code: 'capability.denied' });++ if (!(claims.aud || []).includes('openvibe.services') || !(claims.cap || []).includes('services.release.manage')) return reply(403, { code: 'capability.denied' });const body = JSON.parse(init.body || '{}');if (m[1]) {if (claims.sub !== `app:${m[1]}` || !body.manifest || body.manifest.id !== m[1]) return reply(403, { code: 'release.forbidden' });@@@ -73,7 +73,7 @@ function withAccounts(platform, network = PRODUCTION_NETWORK) {const token = platform.signAppToken(`app_${ex[1].slice(4)}`, { audience: body.audience, capabilities: caps, projectId: ex[1], env: body.env || 'production' });return reply(201, { access_token: token, token_type: 'Bearer', expires_in: 300, scope: caps.join(' ') });}-- if (url.origin === PRODUCTION_CODES) return codes(url, method, init);++ if (url.origin === PRODUCTION_SERVICES) return services(url, method, init);return platform.fetch(input, init);};}@@@ -9,14 +9,14 @@* 2. discovery GET /.well-known/openvibe, as any origin may* 3. project a new project (sandbox) through /api/v1/projects (openvibe-sdk/projects)* 4. app a confidential sandbox app; its secret is held in memory only-- * 5. grants media.object.upload|read, events.app.publish|read and codes.release.manage,++ * 5. grants media.object.upload|read, events.app.publish|read and services.release.manage,* approved at once for the project owner inside the sandbox allowance* 6. media examples/media-uploader: upload a small file, read it back, delete it* 7. events examples/event-subscriber: publish app.<project_key>.developer_path.ran, pull it* 8. export as the project's owner: Network mints read-only export tokens (5 minutes); Media* lists the project's sandbox files with one and refuses an upload; Events returns the* event from step 7 with the other (roadmap WS-N task 9, the project export)-- * 9. release as the app, on OpenVibe.Codes: a sandbox release of itself goes draft →++ * 9. release as the app, on OpenVibe.Services: a sandbox release of itself goes draft →* published → deprecated → revoked, checked in the public list at each step (a* draft is never listed; a revoked release stays listed, marked revoked)* (roadmap WS-N task 5, the app release flow in production)@@@ -35,7 +35,7 @@** Credentials come from the environment only (or ./.env). It refuses to start without them, and* refuses to run in CI against the production Network (CI set and OV_NETWORK_URL unset or-- * openvibe.network). Optional: OV_NETWORK_URL, OV_CODES_URL. It never prints a password, secret or token: they++ * openvibe.network). Optional: OV_NETWORK_URL, OV_SERVICES_URL. It never prints a password, secret or token: they* are masked in all output, and signed URLs are shown without their signature. Exit 0 when every* step passed, 1 when one failed, 2 when it refused to start.*/@@@ -52,10 +52,10 @@ const { publishAppEvent, loadConfig: publishConfig } = require(path.join(ROOT, 'const { createSubscriber, createCursorStore, loadConfig: subscriberConfig } = require(path.join(ROOT, 'examples/event-subscriber/subscriber'));const PRODUCTION_NETWORK = 'https://openvibe.network';--const PRODUCTION_CODES = 'https://openvibe.codes';++const PRODUCTION_SERVICES = 'https://openvibe.services';const PRODUCTION_MEDIA = 'https://openvibe.media';const PRODUCTION_EVENTS = 'https://openvibe.events';--const GRANTS = ['media.object.upload', 'media.object.read', 'events.app.publish', 'events.app.read', 'codes.release.manage'];++const GRANTS = ['media.object.upload', 'media.object.read', 'events.app.publish', 'events.app.read', 'services.release.manage'];const STEPS = ['account', 'discovery', 'project', 'app', 'grants', 'media', 'events', 'export', 'release', 'credentials', 'cleanup'];const STEP_TIMEOUT_MS = 120000;@@@ -102,9 +102,9 @@ async function tryToken(fetchImpl, network, clientId, clientSecret, audience) {* called with every password, secret and token as soon as it is known, so the caller can mask it.* → { ok, steps: [{ name, ok, skipped?, error? }], projectId, appId }*/--async function runDeveloperPath({ network = PRODUCTION_NETWORK, codes = PRODUCTION_CODES, media = PRODUCTION_MEDIA, events = PRODUCTION_EVENTS, fetch: fetchImpl = globalThis.fetch, account, log = (m) => console.log(m), onSecret = () => {}, now = () => new Date() }) {++async function runDeveloperPath({ network = PRODUCTION_NETWORK, services = PRODUCTION_SERVICES, media = PRODUCTION_MEDIA, events = PRODUCTION_EVENTS, fetch: fetchImpl = globalThis.fetch, account, log = (m) => console.log(m), onSecret = () => {}, now = () => new Date() }) {network = network.replace(/\/+$/, '');-- codes = codes.replace(/\/+$/, '');++ services = services.replace(/\/+$/, '');const say = (m) => log(` ${m}`);const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-developer-path-'));const st = { token: null, projects: null, projectId: null, appId: null, secret: null, credentialId: null, newSecret: null };@@@ -256,10 +256,10 @@ async function runDeveloperPath({ network = PRODUCTION_NETWORK, codes = PRODUCTI});await step('release', async () => {-- const token = await appToken(fetchImpl, network, st.appId, st.secret, 'openvibe.codes');++ const token = await appToken(fetchImpl, network, st.appId, st.secret, 'openvibe.services');onSecret(token);const call = async (method, p, body, auth = true) => {-- const res = await fetchImpl(`${codes}/api/v1${p}`, {++ const res = await fetchImpl(`${services}/api/v1${p}`, {method, headers: { Accept: 'application/json', ...(body ? { 'Content-Type': 'application/json' } : {}), ...(auth ? { Authorization: `Bearer ${token}` } : {}) },body: body ? JSON.stringify(body) : undefined,});@@@ -277,7 +277,7 @@ async function runDeveloperPath({ network = PRODUCTION_NETWORK, codes = PRODUCTIif (!draft || draft.status !== 'draft') throw new Error(`the new release is ${draft && draft.status}, not draft`);if (await listed(draft.id)) throw new Error('a draft is listed publicly');say(`draft ${draft.id} (${version})`);-- // Codes lists every release but drafts, a revoked one included (marked revoked), so people see it was pulled.++ // Services lists every release but drafts, a revoked one included (marked revoked), so people see it was pulled.const steps = [['publish', {}, 'published'], ['deprecate', { reason: 'developer path check' }, 'deprecated'], ['revoke', { reason: 'developer path check done' }, 'revoked']];for (const [action, body, want] of steps) {const r = await call('POST', `/releases/${draft.id}/${action}`, body);@@@ -358,8 +358,8 @@ async function main(argv = process.argv.slice(2), env = process.env) {const onSecret = maskOutput();console.log(`OpenVibe developer path against ${network}\n`);const started = Date.now();-- const codes = (env.OV_CODES_URL || PRODUCTION_CODES).replace(/\/+$/, '');-- const result = await runDeveloperPath({ network, codes, media: env.OV_MEDIA_URL || PRODUCTION_MEDIA, events: env.OV_EVENTS_URL || PRODUCTION_EVENTS, account, onSecret });++ const services = (env.OV_SERVICES_URL || PRODUCTION_SERVICES).replace(/\/+$/, '');++ const result = await runDeveloperPath({ network, services, media: env.OV_MEDIA_URL || PRODUCTION_MEDIA, events: env.OV_EVENTS_URL || PRODUCTION_EVENTS, account, onSecret });// --result <file>: a JSON record for the scheduled production run (OpenVibe.Host openvibe-devpath.timer,// shown on openvibe.network/status): step names and outcomes only — never an error text, URL, id or secret.const out = argv.indexOf('--result');@@@ -378,7 +378,7 @@ function writeResult(file, result, { network, started = Date.now(), now = Date.nreturn record;}--module.exports = { runDeveloperPath, writeResult, GRANTS, STEPS, PRODUCTION_NETWORK, PRODUCTION_CODES };++module.exports = { runDeveloperPath, writeResult, GRANTS, STEPS, PRODUCTION_NETWORK, PRODUCTION_SERVICES };if (require.main === module) {main().then((code) => { process.exitCode = code; }, (err) => { console.error(`developer-path: ${describeError(err)}`); process.exitCode = 1; });@@@ -12,7 +12,7 @@ const path = require('node:path');const ROOT = path.join(__dirname, '..');const EXPECTED = ['browser-app', 'chat-bot', 'event-subscriber', 'media-uploader', 'mod-manifest', 'node-server-app', 'oauth-app', 'tool-job', 'webhook-consumer'];--const SDK = 'https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0';++const SDK = 'https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2';const dirs = fs.readdirSync(path.join(ROOT, 'examples')).filter((d) => fs.statSync(path.join(ROOT, 'examples', d)).isDirectory()).sort();assert.deepEqual(dirs, EXPECTED, 'the nine charter examples');
@@ -9,7 +9,7 @@ of them (Media, Events pull, a Tools job) against the real platform with your ow
@@ -9,7 +9,7 @@ of them (Media, Events pull, a Tools job) against the real platform with your ow
is not part of CI or `npm test`, and it was not run against production as part of this release.
is not part of CI or `npm test`, and it was not run against production as part of this release.
`npm run developer-path` is the Wave 20 exit check (account to Media and Events to revoked
`npm run developer-path` is the Wave 20 exit check (account to Media and Events to revoked
credentials); CI runs it against the mock platform, and a person runs it against production.
credentials); CI runs it against the mock platform, and a person runs it against production.
-**Built on:** [openvibe-sdk v0.35.0](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.35.0), and
+**Built on:** [openvibe-sdk v0.35.2](https://github.com/OpenVibers/OpenVibe.SDK/tree/v0.35.2), and
openvibe-contracts v0.112.0 for the mod manifest.
openvibe-contracts v0.112.0 for the mod manifest.
**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §15.1
**Plan:** OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026), §15.1
and §18.9; roadmap Wave 20, §30 (public SDK surface), ADR-014 (developer projects).
and §18.9; roadmap Wave 20, §30 (public SDK surface), ADR-014 (developer projects).
@@ -71,7 +71,7 @@ capabilities approved at once.
@@ -71,7 +71,7 @@ capabilities approved at once.
**2. Create a project, a sandbox app and its grants.** Either:
**2. Create a project, a sandbox app and its grants.** Either:
-- **in the developer portal, <https://openvibe.codes>:** sign in, create a project, add a
+- **in the developer console, <https://openvibe.services>:** sign in, create a project, add a
**confidential** app in the `sandbox` environment with the redirect URI
**confidential** app in the `sandbox` environment with the redirect URI
`http://localhost:3009/callback` (for [oauth-app](examples/oauth-app)), copy the client secret
`http://localhost:3009/callback` (for [oauth-app](examples/oauth-app)), copy the client secret
(it is shown **once**), and request the grants `media.object.upload`, `media.object.read`,
(it is shown **once**), and request the grants `media.object.upload`, `media.object.read`,
@@ -160,11 +160,11 @@ scoped credentials only, then rotates and revokes those credentials and cleans u
@@ -160,11 +160,11 @@ scoped credentials only, then rotates and revokes those credentials and cleans u
| 2. discovery | reads `/.well-known/openvibe` as any origin may |
| 2. discovery | reads `/.well-known/openvibe` as any origin may |
| 3. project | creates a sandbox project with `openvibe-sdk/projects` |
| 3. project | creates a sandbox project with `openvibe-sdk/projects` |
| 4. app | creates a confidential sandbox app; its secret stays in memory |
| 4. app | creates a confidential sandbox app; its secret stays in memory |
-| 5. grants | requests `media.object.upload`, `media.object.read`, `events.app.publish`, `events.app.read`, `codes.release.manage`; all must be approved at once |
+| 5. grants | requests `media.object.upload`, `media.object.read`, `events.app.publish`, `events.app.read`, `services.release.manage`; all must be approved at once |
| 6. media | [media-uploader](examples/media-uploader): upload a small file, read it back, delete it |
| 6. media | [media-uploader](examples/media-uploader): upload a small file, read it back, delete it |
| 7. events | [event-subscriber](examples/event-subscriber): publish `app.<project_key>.developer_path.ran`, pull it with a cursor |
| 7. events | [event-subscriber](examples/event-subscriber): publish `app.<project_key>.developer_path.ran`, pull it with a cursor |
| 8. 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 |
| 8. 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 |
-| 9. release | as the app, on OpenVibe.Codes (`OV_CODES_URL`): a sandbox release of itself goes draft → published → deprecated → revoked, checked in the public list each time |
+| 9. 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 |
| 10. credentials | rotates with no overlap and revokes the first credential; the old secret must be refused at `/oauth/token`, the new one must work |
| 10. credentials | rotates with no overlap and revokes the first credential; the old secret must be refused at `/oauth/token`, the new one must work |
| 11. cleanup | archives the project, even when an earlier step failed; the app's secret must then be refused |
| 11. cleanup | archives the project, even when an earlier step failed; the app's secret must then be refused |
@@ -172,7 +172,7 @@ scoped credentials only, then rotates and revokes those credentials and cleans u
@@ -172,7 +172,7 @@ scoped credentials only, then rotates and revokes those credentials and cleans u
`openvibe-sdk/testing`'s mock platform (the `developer-path` job; `test/developer-path.test.js`
`openvibe-sdk/testing`'s mock platform (the `developer-path` job; `test/developer-path.test.js`
also checks it leaves nothing behind, never prints a secret, and still cleans up after a failure).
also checks it leaves nothing behind, never prints a secret, and still cleans up after a failure).
The mock has no account API, so `scripts/developer-path-mock.js` adds register and login routes
The mock has no account API, so `scripts/developer-path-mock.js` adds register and login routes
-that mint mock user tokens, mints export tokens, and stands in for Codes' release API; every other step is the platform mock's own.
+that mint mock user tokens, mints export tokens, and stands in for Services' release API; every other step is the platform mock's own.
**Against production** it runs only when a person starts it. Credentials come from the
**Against production** it runs only when a person starts it. Credentials come from the
environment (or `./.env`) only:
environment (or `./.env`) only:
@@ -191,7 +191,7 @@ secret and token in its output and prints signed URLs without their signature. E
@@ -191,7 +191,7 @@ secret and token in its output and prints signed URLs without their signature. E
eleven steps passed, `1` when one failed, `2` when it refused to start.
eleven steps passed, `1` when one failed, `2` when it refused to start.
It does not yet cover webhook delivery to an external endpoint (that needs a public https host)
It does not yet cover webhook delivery to an external endpoint (that needs a public https host)
-or publishing an app release in OpenVibe.Codes.
+or publishing an app release in OpenVibe.Services.
### Tools job proof
### Tools job proof
@@ -265,7 +265,7 @@ Repository checks (`test/`):
@@ -265,7 +265,7 @@ Repository checks (`test/`):
value in a `.env.example`.
value in a `.env.example`.
- `structure.test.js` checks the nine examples are complete and copyable: README with "What it
- `structure.test.js` checks the nine examples are complete and copyable: README with "What it
proves" and "Run it against the real platform", a `.env.example` that names every variable the
proves" and "Run it against the real platform", a `.env.example` that names every variable the
- code reads, MIT license, `openvibe-sdk` pinned to the v0.35.0 tag, no `file:` links; and that
+ code reads, MIT license, `openvibe-sdk` pinned to the v0.35.2 tag, no `file:` links; and that
`npm run e2e` is outside CI and `npm test` and refuses to start without its variables; and that
`npm run e2e` is outside CI and `npm test` and refuses to start without its variables; and that
CI runs the developer path against the mock platform only.
CI runs the developer path against the mock platform only.
- `developer-path.test.js` runs the developer path against the mock platform and checks the
- `developer-path.test.js` runs the developer path against the mock platform and checks the
@@ -294,11 +294,11 @@ test/public-surface.test.js, test/structure.test.js
@@ -294,11 +294,11 @@ test/public-surface.test.js, test/structure.test.js
## Does not own
## Does not own
- any platform service, the SDK (OpenVibe.SDK), the contracts (OpenVibe.Contracts) or the
- any platform service, the SDK (OpenVibe.SDK), the contracts (OpenVibe.Contracts) or the
- developer portal (OpenVibe.Codes)
+ developer console (OpenVibe.Services)
## Depends on
## Depends on
-- OpenVibe.SDK (v0.35.0), OpenVibe.Contracts (v0.112.0, mod manifest only)
+- OpenVibe.SDK (v0.35.2), OpenVibe.Contracts (v0.113.0, mod manifest only)
- at run time against the real platform: Network (tokens, registry, developer projects), Events,
- at run time against the real platform: Network (tokens, registry, developer projects), Events,
Media, Tools, Chat (on openvibe.live)
Media, Tools, Chat (on openvibe.live)
@@ -308,7 +308,7 @@ Examples implements no capability (its manifest lists none). The examples call t
@@ -308,7 +308,7 @@ Examples implements no capability (its manifest lists none). The examples call t
developer app with the grants a sandbox project asks for (`npm run new-app`): `media.object.upload`
developer app with the grants a sandbox project asks for (`npm run new-app`): `media.object.upload`
and `media.object.read` (Media), `events.app.publish`, `events.app.read` and `events.app.subscribe`
and `media.object.read` (Media), `events.app.publish`, `events.app.read` and `events.app.subscribe`
(Events), and `tools.job.create`, `tools.job.read` and `tools.job.cancel` (Tools); a public browser app
(Events), and `tools.job.create`, `tools.job.read` and `tools.job.cancel` (Tools); a public browser app
-gets `media.object.read` only. The developer path check also uses `codes.release.manage` (Codes). The
+gets `media.object.read` only. The developer path check also uses `services.release.manage` (Services). The
chat bot uses a Live `hbt_` token with scope `chat`, because `chat.message.send` is first-party.
chat bot uses a Live `hbt_` token with scope `chat`, because `chat.message.send` is first-party.
## Acceptance
## Acceptance
@@ -349,5 +349,5 @@ own app.
@@ -349,5 +349,5 @@ own app.
Part of the [OpenVibe network](https://openvibe.network). Built in the open by [OpenVibers](https://github.com/OpenVibers).
Part of the [OpenVibe network](https://openvibe.network). Built in the open by [OpenVibers](https://github.com/OpenVibers).
<!-- versions:start -->
<!-- versions:start -->
-- openvibe-sdk: v0.35.0
+- openvibe-sdk: v0.35.2
<!-- versions:end -->
<!-- versions:end -->
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
"code": true,
"code": true,
"wave": "W20",
"wave": "W20",
"version": "0.3.0",
"version": "0.3.0",
- "sdk": "openvibe-sdk v0.35.0",
+ "sdk": "openvibe-sdk v0.35.2",
"contracts": "openvibe-contracts v0.49.0 (mod-manifest example)",
"contracts": "openvibe-contracts v0.49.0 (mod-manifest example)",
"deployed": false,
"deployed": false,
"deployment": "none: a repository with CI, not a runtime and not a published package",
"deployment": "none: a repository with CI, not a runtime and not a published package",
@@ -13,6 +13,6 @@
@@ -13,6 +13,6 @@
"node": ">=22"
"node": ">=22"
},
},
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
}
}
}
}
@@ -13,6 +13,6 @@
@@ -13,6 +13,6 @@
"node": ">=22"
"node": ">=22"
},
},
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
}
}
}
}
@@ -13,6 +13,6 @@
@@ -13,6 +13,6 @@
"node": ">=22"
"node": ">=22"
},
},
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
}
}
}
}
@@ -13,7 +13,7 @@
@@ -13,7 +13,7 @@
"node": ">=22"
"node": ">=22"
},
},
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0",
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.112.0"
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.112.0"
}
}
}
}
@@ -13,6 +13,6 @@
@@ -13,6 +13,6 @@
"node": ">=22"
"node": ">=22"
},
},
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
}
}
}
}
@@ -13,6 +13,6 @@
@@ -13,6 +13,6 @@
"node": ">=22"
"node": ">=22"
},
},
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
}
}
}
}
@@ -13,6 +13,6 @@
@@ -13,6 +13,6 @@
"node": ">=22"
"node": ">=22"
},
},
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
}
}
}
}
@@ -13,7 +13,7 @@
@@ -13,7 +13,7 @@
"node": ">=22"
"node": ">=22"
},
},
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0",
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2",
"better-sqlite3": "^11.10.0"
"better-sqlite3": "^11.10.0"
}
}
}
}
@@ -15,7 +15,7 @@
@@ -15,7 +15,7 @@
"create-openvibe-app": "scripts/create-openvibe-app.js"
"create-openvibe-app": "scripts/create-openvibe-app.js"
},
},
"devDependencies": {
"devDependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
},
},
"engines": {
"engines": {
"node": ">=22"
"node": ">=22"
@@ -26,7 +26,7 @@
@@ -26,7 +26,7 @@
"version": "0.2.0",
"version": "0.2.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
},
},
"engines": {
"engines": {
"node": ">=22"
"node": ">=22"
@@ -48,7 +48,7 @@
@@ -48,7 +48,7 @@
"version": "0.2.0",
"version": "0.2.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
},
},
"engines": {
"engines": {
"node": ">=22"
"node": ">=22"
@@ -59,7 +59,7 @@
@@ -59,7 +59,7 @@
"version": "0.2.0",
"version": "0.2.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
},
},
"engines": {
"engines": {
"node": ">=22"
"node": ">=22"
@@ -71,7 +71,7 @@
@@ -71,7 +71,7 @@
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.112.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.112.0",
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
},
},
"engines": {
"engines": {
"node": ">=22"
"node": ">=22"
@@ -82,7 +82,7 @@
@@ -82,7 +82,7 @@
"version": "0.2.0",
"version": "0.2.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
},
},
"engines": {
"engines": {
"node": ">=22"
"node": ">=22"
@@ -93,7 +93,7 @@
@@ -93,7 +93,7 @@
"version": "0.2.0",
"version": "0.2.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
},
},
"engines": {
"engines": {
"node": ">=22"
"node": ">=22"
@@ -104,7 +104,7 @@
@@ -104,7 +104,7 @@
"version": "0.2.0",
"version": "0.2.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
},
},
"engines": {
"engines": {
"node": ">=22"
"node": ">=22"
@@ -116,7 +116,7 @@
@@ -116,7 +116,7 @@
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"better-sqlite3": "^11.10.0",
"better-sqlite3": "^11.10.0",
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
},
},
"engines": {
"engines": {
"node": ">=22"
"node": ">=22"
@@ -472,9 +472,10 @@
@@ -472,9 +472,10 @@
"link": true
"link": true
},
},
"node_modules/openvibe-sdk": {
"node_modules/openvibe-sdk": {
- "version": "0.35.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0",
- "integrity": "sha512-IF7XB97tU7Q+ccwssGx6QXXcfDPcVpl+mX3BjIr7C6NJlcmoqj9DbHiKrBxlVav0eG44Jf8zuXEwokrHDq3J0w==",
+ "version": "0.35.2",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2",
+ "integrity": "sha512-HudKZJK3lZ9i0+yRdMrz9pJmIrYNTN8AFqbHFCz5jDCF68CfZwUJQyhE61pHODzGRyTvUi4DtAzkd0WEzIWx/g==",
+ "dev": true,
"license": "MIT",
"license": "MIT",
"engines": {
"engines": {
"node": ">=20"
"node": ">=20"
@@ -23,6 +23,6 @@
@@ -23,6 +23,6 @@
"node": ">=22"
"node": ">=22"
},
},
"devDependencies": {
"devDependencies": {
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2"
}
}
}
}
@@ -10,21 +10,21 @@
@@ -10,21 +10,21 @@
* the flow uses, /api/auth/register and /api/auth/login, as a thin layer: register creates a mock
* the flow uses, /api/auth/register and /api/auth/login, as a thin layer: register creates a mock
* user and returns a Network user token for it; login returns one for an existing user (the mock
* user and returns a Network user token for it; login returns one for an existing user (the mock
* does not check passwords). It also mints Network's export tokens (the export step, signed by the
* does not check passwords). It also mints Network's export tokens (the export step, signed by the
- * platform like any app token) and stands in for OpenVibe.Codes' release API (the release step):
- * an app token for openvibe.codes carrying codes.release.manage manages the app's own releases,
- * draft → published → deprecated → revoked, and the public list shows every release but drafts, like Codes.
+ * platform like any app token) and stands in for OpenVibe.Services' release API (the release step):
+ * an app token for openvibe.services carrying services.release.manage manages the app's own releases,
+ * draft → published → deprecated → revoked, and the public list shows every release but drafts, like Services.
* Everything else is the platform mock's own.
* Everything else is the platform mock's own.
*/
*/
const { createMockPlatform } = require('openvibe-sdk/testing');
const { createMockPlatform } = require('openvibe-sdk/testing');
-const { runDeveloperPath, PRODUCTION_NETWORK, PRODUCTION_CODES } = require('./developer-path');
+const { runDeveloperPath, PRODUCTION_NETWORK, PRODUCTION_SERVICES } = require('./developer-path');
function withAccounts(platform, network = PRODUCTION_NETWORK) {
function withAccounts(platform, network = PRODUCTION_NETWORK) {
const byName = new Map();
const byName = new Map();
const reply = (status, body) => new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } });
const reply = (status, body) => new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } });
- // OpenVibe.Codes' release API, in memory (the token's signature is the platform's; here its claims decide).
+ // OpenVibe.Services' release API, in memory (the token's signature is the platform's; here its claims decide).
const releases = new Map();
const releases = new Map();
let n = 0;
let n = 0;
- function codes(url, method, init) {
+ function services(url, method, init) {
const m = url.pathname.match(/^\/api\/v1\/(?:apps\/(app_[0-9A-Z]+)\/releases|releases\/([a-z0-9_]+)\/(publish|deprecate|revoke))$/i);
const m = url.pathname.match(/^\/api\/v1\/(?:apps\/(app_[0-9A-Z]+)\/releases|releases\/([a-z0-9_]+)\/(publish|deprecate|revoke))$/i);
if (!m) return reply(404, { code: 'route.not_found' });
if (!m) return reply(404, { code: 'route.not_found' });
const list = (appId) => [...releases.values()].filter((r) => r.app_id === appId && r.status !== 'draft');
const list = (appId) => [...releases.values()].filter((r) => r.app_id === appId && r.status !== 'draft');
@@ -32,7 +32,7 @@ function withAccounts(platform, network = PRODUCTION_NETWORK) {
@@ -32,7 +32,7 @@ function withAccounts(platform, network = PRODUCTION_NETWORK) {
let claims = null;
let claims = null;
try { claims = JSON.parse(Buffer.from(String((init.headers || {}).Authorization || '').replace(/^Bearer /, '').split('.')[1], 'base64url').toString('utf8')); } catch { claims = null; }
try { claims = JSON.parse(Buffer.from(String((init.headers || {}).Authorization || '').replace(/^Bearer /, '').split('.')[1], 'base64url').toString('utf8')); } catch { claims = null; }
if (!claims) return reply(401, { code: 'auth.required' });
if (!claims) return reply(401, { code: 'auth.required' });
- if (!(claims.aud || []).includes('openvibe.codes') || !(claims.cap || []).includes('codes.release.manage')) return reply(403, { code: 'capability.denied' });
+ if (!(claims.aud || []).includes('openvibe.services') || !(claims.cap || []).includes('services.release.manage')) return reply(403, { code: 'capability.denied' });
const body = JSON.parse(init.body || '{}');
const body = JSON.parse(init.body || '{}');
if (m[1]) {
if (m[1]) {
if (claims.sub !== `app:${m[1]}` || !body.manifest || body.manifest.id !== m[1]) return reply(403, { code: 'release.forbidden' });
if (claims.sub !== `app:${m[1]}` || !body.manifest || body.manifest.id !== m[1]) return reply(403, { code: 'release.forbidden' });
@@ -73,7 +73,7 @@ function withAccounts(platform, network = PRODUCTION_NETWORK) {
@@ -73,7 +73,7 @@ function withAccounts(platform, network = PRODUCTION_NETWORK) {
const token = platform.signAppToken(`app_${ex[1].slice(4)}`, { audience: body.audience, capabilities: caps, projectId: ex[1], env: body.env || 'production' });
const token = platform.signAppToken(`app_${ex[1].slice(4)}`, { audience: body.audience, capabilities: caps, projectId: ex[1], env: body.env || 'production' });
return reply(201, { access_token: token, token_type: 'Bearer', expires_in: 300, scope: caps.join(' ') });
return reply(201, { access_token: token, token_type: 'Bearer', expires_in: 300, scope: caps.join(' ') });
}
}
- if (url.origin === PRODUCTION_CODES) return codes(url, method, init);
+ if (url.origin === PRODUCTION_SERVICES) return services(url, method, init);
return platform.fetch(input, init);
return platform.fetch(input, init);
};
};
}
}
@@ -9,14 +9,14 @@
@@ -9,14 +9,14 @@
* 2. discovery GET /.well-known/openvibe, as any origin may
* 2. discovery GET /.well-known/openvibe, as any origin may
* 3. project a new project (sandbox) through /api/v1/projects (openvibe-sdk/projects)
* 3. project a new project (sandbox) through /api/v1/projects (openvibe-sdk/projects)
* 4. app a confidential sandbox app; its secret is held in memory only
* 4. app a confidential sandbox app; its secret is held in memory only
- * 5. grants media.object.upload|read, events.app.publish|read and codes.release.manage,
+ * 5. grants media.object.upload|read, events.app.publish|read and services.release.manage,
* approved at once for the project owner inside the sandbox allowance
* approved at once for the project owner inside the sandbox allowance
* 6. media examples/media-uploader: upload a small file, read it back, delete it
* 6. media examples/media-uploader: upload a small file, read it back, delete it
* 7. events examples/event-subscriber: publish app.<project_key>.developer_path.ran, pull it
* 7. events examples/event-subscriber: publish app.<project_key>.developer_path.ran, pull it
* 8. export as the project's owner: Network mints read-only export tokens (5 minutes); Media
* 8. export as the project's owner: Network mints read-only export tokens (5 minutes); Media
* lists the project's sandbox files with one and refuses an upload; Events returns the
* lists the project's sandbox files with one and refuses an upload; Events returns the
* event from step 7 with the other (roadmap WS-N task 9, the project export)
* event from step 7 with the other (roadmap WS-N task 9, the project export)
- * 9. release as the app, on OpenVibe.Codes: a sandbox release of itself goes draft →
+ * 9. release as the app, on OpenVibe.Services: a sandbox release of itself goes draft →
* published → deprecated → revoked, checked in the public list at each step (a
* published → deprecated → revoked, checked in the public list at each step (a
* draft is never listed; a revoked release stays listed, marked revoked)
* draft is never listed; a revoked release stays listed, marked revoked)
* (roadmap WS-N task 5, the app release flow in production)
* (roadmap WS-N task 5, the app release flow in production)
@@ -35,7 +35,7 @@
@@ -35,7 +35,7 @@
*
*
* Credentials come from the environment only (or ./.env). It refuses to start without them, and
* Credentials come from the environment only (or ./.env). It refuses to start without them, and
* refuses to run in CI against the production Network (CI set and OV_NETWORK_URL unset or
* refuses to run in CI against the production Network (CI set and OV_NETWORK_URL unset or
- * openvibe.network). Optional: OV_NETWORK_URL, OV_CODES_URL. It never prints a password, secret or token: they
+ * openvibe.network). Optional: OV_NETWORK_URL, OV_SERVICES_URL. It never prints a password, secret or token: they
* are masked in all output, and signed URLs are shown without their signature. Exit 0 when every
* are masked in all output, and signed URLs are shown without their signature. Exit 0 when every
* step passed, 1 when one failed, 2 when it refused to start.
* step passed, 1 when one failed, 2 when it refused to start.
*/
*/
@@ -52,10 +52,10 @@ const { publishAppEvent, loadConfig: publishConfig } = require(path.join(ROOT, '
@@ -52,10 +52,10 @@ const { publishAppEvent, loadConfig: publishConfig } = require(path.join(ROOT, '
const { createSubscriber, createCursorStore, loadConfig: subscriberConfig } = require(path.join(ROOT, 'examples/event-subscriber/subscriber'));
const { createSubscriber, createCursorStore, loadConfig: subscriberConfig } = require(path.join(ROOT, 'examples/event-subscriber/subscriber'));
const PRODUCTION_NETWORK = 'https://openvibe.network';
const PRODUCTION_NETWORK = 'https://openvibe.network';
-const PRODUCTION_CODES = 'https://openvibe.codes';
+const PRODUCTION_SERVICES = 'https://openvibe.services';
const PRODUCTION_MEDIA = 'https://openvibe.media';
const PRODUCTION_MEDIA = 'https://openvibe.media';
const PRODUCTION_EVENTS = 'https://openvibe.events';
const PRODUCTION_EVENTS = 'https://openvibe.events';
-const GRANTS = ['media.object.upload', 'media.object.read', 'events.app.publish', 'events.app.read', 'codes.release.manage'];
+const GRANTS = ['media.object.upload', 'media.object.read', 'events.app.publish', 'events.app.read', 'services.release.manage'];
const STEPS = ['account', 'discovery', 'project', 'app', 'grants', 'media', 'events', 'export', 'release', 'credentials', 'cleanup'];
const STEPS = ['account', 'discovery', 'project', 'app', 'grants', 'media', 'events', 'export', 'release', 'credentials', 'cleanup'];
const STEP_TIMEOUT_MS = 120000;
const STEP_TIMEOUT_MS = 120000;
@@ -102,9 +102,9 @@ async function tryToken(fetchImpl, network, clientId, clientSecret, audience) {
@@ -102,9 +102,9 @@ async function tryToken(fetchImpl, network, clientId, clientSecret, audience) {
* called with every password, secret and token as soon as it is known, so the caller can mask it.
* called with every password, secret and token as soon as it is known, so the caller can mask it.
* → { ok, steps: [{ name, ok, skipped?, error? }], projectId, appId }
* → { ok, steps: [{ name, ok, skipped?, error? }], projectId, appId }
*/
*/
-async function runDeveloperPath({ network = PRODUCTION_NETWORK, codes = PRODUCTION_CODES, media = PRODUCTION_MEDIA, events = PRODUCTION_EVENTS, fetch: fetchImpl = globalThis.fetch, account, log = (m) => console.log(m), onSecret = () => {}, now = () => new Date() }) {
+async function runDeveloperPath({ network = PRODUCTION_NETWORK, services = PRODUCTION_SERVICES, media = PRODUCTION_MEDIA, events = PRODUCTION_EVENTS, fetch: fetchImpl = globalThis.fetch, account, log = (m) => console.log(m), onSecret = () => {}, now = () => new Date() }) {
network = network.replace(/\/+$/, '');
network = network.replace(/\/+$/, '');
- codes = codes.replace(/\/+$/, '');
+ services = services.replace(/\/+$/, '');
const say = (m) => log(` ${m}`);
const say = (m) => log(` ${m}`);
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-developer-path-'));
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-developer-path-'));
const st = { token: null, projects: null, projectId: null, appId: null, secret: null, credentialId: null, newSecret: null };
const st = { token: null, projects: null, projectId: null, appId: null, secret: null, credentialId: null, newSecret: null };
@@ -256,10 +256,10 @@ async function runDeveloperPath({ network = PRODUCTION_NETWORK, codes = PRODUCTI
@@ -256,10 +256,10 @@ async function runDeveloperPath({ network = PRODUCTION_NETWORK, codes = PRODUCTI
});
});
await step('release', async () => {
await step('release', async () => {
- const token = await appToken(fetchImpl, network, st.appId, st.secret, 'openvibe.codes');
+ const token = await appToken(fetchImpl, network, st.appId, st.secret, 'openvibe.services');
onSecret(token);
onSecret(token);
const call = async (method, p, body, auth = true) => {
const call = async (method, p, body, auth = true) => {
- const res = await fetchImpl(`${codes}/api/v1${p}`, {
+ const res = await fetchImpl(`${services}/api/v1${p}`, {
method, headers: { Accept: 'application/json', ...(body ? { 'Content-Type': 'application/json' } : {}), ...(auth ? { Authorization: `Bearer ${token}` } : {}) },
method, headers: { Accept: 'application/json', ...(body ? { 'Content-Type': 'application/json' } : {}), ...(auth ? { Authorization: `Bearer ${token}` } : {}) },
body: body ? JSON.stringify(body) : undefined,
body: body ? JSON.stringify(body) : undefined,
});
});
@@ -277,7 +277,7 @@ async function runDeveloperPath({ network = PRODUCTION_NETWORK, codes = PRODUCTI
@@ -277,7 +277,7 @@ async function runDeveloperPath({ network = PRODUCTION_NETWORK, codes = PRODUCTI
if (!draft || draft.status !== 'draft') throw new Error(`the new release is ${draft && draft.status}, not draft`);
if (!draft || draft.status !== 'draft') throw new Error(`the new release is ${draft && draft.status}, not draft`);
if (await listed(draft.id)) throw new Error('a draft is listed publicly');
if (await listed(draft.id)) throw new Error('a draft is listed publicly');
say(`draft ${draft.id} (${version})`);
say(`draft ${draft.id} (${version})`);
- // Codes lists every release but drafts, a revoked one included (marked revoked), so people see it was pulled.
+ // Services lists every release but drafts, a revoked one included (marked revoked), so people see it was pulled.
const steps = [['publish', {}, 'published'], ['deprecate', { reason: 'developer path check' }, 'deprecated'], ['revoke', { reason: 'developer path check done' }, 'revoked']];
const steps = [['publish', {}, 'published'], ['deprecate', { reason: 'developer path check' }, 'deprecated'], ['revoke', { reason: 'developer path check done' }, 'revoked']];
for (const [action, body, want] of steps) {
for (const [action, body, want] of steps) {
const r = await call('POST', `/releases/${draft.id}/${action}`, body);
const r = await call('POST', `/releases/${draft.id}/${action}`, body);
@@ -358,8 +358,8 @@ async function main(argv = process.argv.slice(2), env = process.env) {
@@ -358,8 +358,8 @@ async function main(argv = process.argv.slice(2), env = process.env) {
const onSecret = maskOutput();
const onSecret = maskOutput();
console.log(`OpenVibe developer path against ${network}\n`);
console.log(`OpenVibe developer path against ${network}\n`);
const started = Date.now();
const started = Date.now();
- const codes = (env.OV_CODES_URL || PRODUCTION_CODES).replace(/\/+$/, '');
- const result = await runDeveloperPath({ network, codes, media: env.OV_MEDIA_URL || PRODUCTION_MEDIA, events: env.OV_EVENTS_URL || PRODUCTION_EVENTS, account, onSecret });
+ const services = (env.OV_SERVICES_URL || PRODUCTION_SERVICES).replace(/\/+$/, '');
+ const result = await runDeveloperPath({ network, services, media: env.OV_MEDIA_URL || PRODUCTION_MEDIA, events: env.OV_EVENTS_URL || PRODUCTION_EVENTS, account, onSecret });
// --result <file>: a JSON record for the scheduled production run (OpenVibe.Host openvibe-devpath.timer,
// --result <file>: a JSON record for the scheduled production run (OpenVibe.Host openvibe-devpath.timer,
// shown on openvibe.network/status): step names and outcomes only — never an error text, URL, id or secret.
// shown on openvibe.network/status): step names and outcomes only — never an error text, URL, id or secret.
const out = argv.indexOf('--result');
const out = argv.indexOf('--result');
@@ -378,7 +378,7 @@ function writeResult(file, result, { network, started = Date.now(), now = Date.n
@@ -378,7 +378,7 @@ function writeResult(file, result, { network, started = Date.now(), now = Date.n
return record;
return record;
}
}
-module.exports = { runDeveloperPath, writeResult, GRANTS, STEPS, PRODUCTION_NETWORK, PRODUCTION_CODES };
+module.exports = { runDeveloperPath, writeResult, GRANTS, STEPS, PRODUCTION_NETWORK, PRODUCTION_SERVICES };
if (require.main === module) {
if (require.main === module) {
main().then((code) => { process.exitCode = code; }, (err) => { console.error(`developer-path: ${describeError(err)}`); process.exitCode = 1; });
main().then((code) => { process.exitCode = code; }, (err) => { console.error(`developer-path: ${describeError(err)}`); process.exitCode = 1; });
@@ -12,7 +12,7 @@ const path = require('node:path');
@@ -12,7 +12,7 @@ const path = require('node:path');
const ROOT = path.join(__dirname, '..');
const ROOT = path.join(__dirname, '..');
const EXPECTED = ['browser-app', 'chat-bot', 'event-subscriber', 'media-uploader', 'mod-manifest', 'node-server-app', 'oauth-app', 'tool-job', 'webhook-consumer'];
const EXPECTED = ['browser-app', 'chat-bot', 'event-subscriber', 'media-uploader', 'mod-manifest', 'node-server-app', 'oauth-app', 'tool-job', 'webhook-consumer'];
-const SDK = 'https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.0';
+const SDK = 'https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.35.2';
const dirs = fs.readdirSync(path.join(ROOT, 'examples')).filter((d) => fs.statSync(path.join(ROOT, 'examples', d)).isDirectory()).sort();
const dirs = fs.readdirSync(path.join(ROOT, 'examples')).filter((d) => fs.statSync(path.join(ROOT, 'examples', d)).isDirectory()).sort();
assert.deepEqual(dirs, EXPECTED, 'the nine charter examples');
assert.deepEqual(dirs, EXPECTED, 'the nine charter examples');