openvibers/openvibe.codes Commits

Codes' outbox is openvibe-sdk/events createServiceOutbox (plan T1): the same event_outbox table, event-type allowlist and relay rules in one call, the hand-rolled wrapper gone. Pins openvibe-sdk v0.25.0, openvibe-contracts v0.79.0, openvibe-shared v2.0.0. Both suites green (verified).

OP OpenVibers committed 29 Sept 2026 · 3408517d5a · 6 files, +40 −83


Changes

ddiff --git a/README.md b/README.md
iindex acd4801..9177ee7 100644
---- a/README.md
++++ b/README.md
@@@ -48,7 +48,7 @@ Everything is server-rendered and usable without JavaScript. The only scripts of
- **OpenVibe.Events** — the outbox relay publishes `codes.app.*` with Codes' service token (`events.event.publish`); the Events playground calls it with the app's token (`events.app.publish`); the project archive pulls the project's app events with a Network export token (`events.app.read`).
- **OpenVibe.Media** — the Media playground uploads with the app's token into the project's namespace; the project archive lists the project's objects, namespaces and download URLs with a Network export token (`media.object.list`, `media.object.read`).
- **PostgreSQL 18 and Valkey 9** (OpenVibe.Host `roles/data/`, ADR-035): every read and write is async through `openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional).
--- **openvibe-contracts** v0.76.0, **openvibe-sdk v0.20.1**, **openvibe-shared v1.27.0** (pinned tag tarballs; the docs show the tag and the package version, and say so when they differ).
++- **openvibe-contracts** v0.79.0, **openvibe-sdk v0.25.0**, **openvibe-shared v2.0.0** (pinned tag tarballs; the docs show the tag and the package version, and say so when they differ).
No path in Codes sends or accepts a shared loopback key (tested by grep and at runtime).
ddiff --git a/STATUS.json b/STATUS.json
iindex 4ca3d7a..c316ea6 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -7,11 +7,11 @@
"runtime": "node 22, express 4, PostgreSQL 18 (openvibe-sdk/db, async) + Valkey 9",
"port": 4900,
"domain": "openvibe.codes",
-- "contracts": "openvibe-contracts v0.76.0",
++ "contracts": "openvibe-contracts v0.79.0",
"packages": [
-- "openvibe-contracts v0.76.0",
-- "openvibe-sdk v0.20.1",
-- "openvibe-shared v1.27.0"
++ "openvibe-contracts v0.79.0",
++ "openvibe-sdk v0.25.0",
++ "openvibe-shared v2.0.0"
],
"summary": "Deployed and public since 2026-09-23: https://openvibe.codes is served by this portal (deployed with `sudo ovhost deploy codes`, unit openvibe-codes on 127.0.0.1:4900 behind nginx), and the domain left OpenVibe.Sites. Sign-in uses the registered Network OAuth client `codes` (PKCE S256); the principal `codes` holds events.event.publish and the Events relay is configured. On 2026-09-23 production held 0 manifests, 0 releases and 0 playground runs. The external-developer path (account, project, sandbox app, grants, Media upload, app events) was run once on production on 2026-09-23 with public endpoints and curl, not through these playgrounds; that check is not committed to a repository or CI.",
"works": [
ddiff --git a/package-lock.json b/package-lock.json
iindex d4f6896..00b3a49 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -16,9 +16,9 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"iovalkey": "^0.4.0",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.23.1",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.29.2",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.25.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.0.0",
"pg": "^8.23.0"
},
"devDependencies": {
@@@ -1157,9 +1157,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.76.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
-- "integrity": "sha512-zTdx776KWrcJKm1JTnf+0+BnsvswdvXy1ldPyse1hSqhnW/SSr98sX696eRetFUhTl8qVM1xSRCykqIUbqItPw==",
++ "version": "0.79.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
++ "integrity": "sha512-FY5GLbiOq0kDw5lUPPzX6h3ATbzvsr/mX5DUCF4GRfn6vGMP0p89rZZDhgtVDaS+yCBCYMZ8zFaZxuHEkEVwVg==",
"license": "AGPL-3.0-only",
"dependencies": {
"ajv": "^8.17.1",
@@@ -1173,9 +1173,9 @@
}
},
"node_modules/openvibe-sdk": {
-- "version": "0.23.1",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.23.1",
-- "integrity": "sha512-6u1Ym8u+nSKyq+uJjel4/3QDQ9HOjpQzFa8oMasorXQhgHUlDEQ7X6F6Xf1zZTuuVdMVkxOeqJqOnQbraktosg==",
++ "version": "0.25.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.25.0",
++ "integrity": "sha512-8eurZLyFUtB8XN16hLUuYkah+qwS3tkvdzul5qdZkJTi0aZ4q8dq2QGWSq6pN5mtVPdJyuJ8Mo53r36lixmP1g==",
"license": "MIT",
"engines": {
"node": ">=20"
@@@ -1202,9 +1202,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.29.2",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.29.2",
-- "integrity": "sha512-w5daRSzujokYT1/Q3fu0KdcSZUBEv1pm66H8vIAI2SY0xtOaLqgfkXJpfIz1NgvJuyK8dhXBbLjM8Ow9RZ5x+A==",
++ "version": "2.0.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.0.0",
++ "integrity": "sha512-n5CHC0hfxOo0jo/3kS4O3KiwhirhZDlx5zZTIuPyIf3Dq0yI/R6R64btx7NYwZe5dI0NjvqzEtnv19m8unwLMg==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex 9c3379e..dd5a539 100644
---- a/package.json
++++ b/package.json
@@@ -28,9 +28,9 @@
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"iovalkey": "^0.4.0",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.23.1",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.29.2",
++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.79.0",
++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.25.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.0.0",
"pg": "^8.23.0"
},
"engines": {
ddiff --git a/server/events/outbox.js b/server/events/outbox.js
iindex 6eaa275..2087829 100644
---- a/server/events/outbox.js
++++ b/server/events/outbox.js
@@@ -15,69 +15,26 @@
* audience openvibe.events) only when EVENTS_URL and OV_OAUTH_CLIENT_SECRET are set; otherwise rows
* wait in event_outbox and /api/ready reports the relay as off. Payloads never carry a secret.
*/
--const { createClient } = require('openvibe-sdk/core');
--const { createServiceTokenClient } = require('openvibe-sdk/auth');
--const { createEventsClient, createPgOutbox } = require('openvibe-sdk/events');
++const { createServiceOutbox } = require('openvibe-sdk/events');
const EVENT_TYPES = ['codes.app.published', 'codes.app.deprecated', 'codes.app.revoked', 'codes.moderation.action'];
++/** Codes' wiring of the SDK's shared outbox (plan T1). */
function createCodesOutbox({ db, config, fetchImpl, now, log = console }) {
-- const enabled = Boolean(config.events.url && config.oauth.clientSecret);
-- const clientOpts = { baseUrls: { events: config.events.url || 'http://127.0.0.1:4300' }, retries: 0, autoDiscover: false };
-- if (fetchImpl) clientOpts.fetch = fetchImpl;
-- if (enabled) {
-- clientOpts.tokenProvider = createServiceTokenClient({
-- tokenUrl: `${config.networkInternalUrl}/oauth/token`, clientId: config.oauth.clientId, clientSecret: config.oauth.clientSecret,
-- scope: { 'openvibe.events': 'events.event.publish' }, ...(fetchImpl ? { fetch: fetchImpl } : {}),
-- });
-- } else {
-- clientOpts.getToken = async () => { throw new Error('events relay disabled (EVENTS_URL / OV_OAUTH_CLIENT_SECRET unset)'); };
-- }
-- const events = createEventsClient(createClient(clientOpts), { source: 'codes' });
-- let lastError = null;
-- // The PostgreSQL outbox: rows are written in the change's own transaction (enqueue(db, …) joins the ambient
-- // transaction); several processes relay one table safely (leases).
-- const outbox = createPgOutbox(db, {
-- events,
++ return createServiceOutbox({
++ db,
++ source: 'codes',
++ eventsUrl: config.events.url,
++ networkInternalUrl: config.networkInternalUrl,
++ clientId: config.oauth.clientId,
++ clientSecret: config.oauth.clientSecret,
intervalMs: config.events.intervalMs,
-- now,
-- onError: (err) => {
-- const msg = err && err.message;
-- if (msg !== lastError) log.warn('[Codes] event publish failed (will retry):', msg);
-- lastError = msg;
-- },
++ log,
++ eventTypes: EVENT_TYPES,
++ autoDiscover: false,
++ ...(fetchImpl ? { fetch: fetchImpl } : {}),
++ ...(now ? { now } : {}),
});
--
-- /** Inside the caller's transaction. Returns the complete envelope (with its event_id). */
-- async function emit(envelope, { traceparent } = {}) {
-- if (!EVENT_TYPES.includes(envelope.event_type)) throw new Error(`Codes does not produce ${envelope.event_type}`);
-- return await outbox.enqueue(db, envelope, { traceparent });
-- }
--
-- /**
-- * codes.moderation.action (common.moderation-action@1), inside the caller's transaction. actor:
-- * the staff member's subject; target: { type, id, owner_subject? }. Never the content.
-- */
-- async function moderationAction({ action, target, actorSubject, reason = null, details = {} }, { traceparent } = {}) {
-- const t = { type: target.type, id: String(target.id).slice(0, 200), owner_subject: target.owner_subject || null };
-- return await emit({
-- event_type: 'codes.moderation.action',
-- actor: actorSubject ? { type: 'user', id: actorSubject } : { type: 'service', id: 'codes' },
-- subject: { type: 'moderation_action', id: `${t.type}:${t.id}`.slice(0, 200) },
-- visibility: 'internal',
-- payload: { action, target: t, actor_subject: actorSubject || null, reason: reason ? String(reason).slice(0, 500) : null, details: details || {} },
-- }, { traceparent });
-- }
--
-- return {
-- emit,
-- moderationAction,
-- outbox,
-- enabled,
-- start() { if (enabled) outbox.start(); },
-- stop: () => outbox.stop(),
-- status: async () => ({ enabled, pending: await outbox.pending(), rejected: await outbox.rejected(), last_error: lastError }),
-- };
}
module.exports = { createCodesOutbox, EVENT_TYPES };
ddiff --git a/test/docs.test.js b/test/docs.test.js
iindex 1e5453d..9be8912 100644
---- a/test/docs.test.js
++++ b/test/docs.test.js
@@@ -19,11 +19,11 @@ const sdkPkg = require('openvibe-sdk/package.json');
await check('package.json pins the contracts, SDK and shared tags that are installed', async () => {
const tag = (dep) => (pkg.dependencies[dep].match(/refs\/tags\/(v[0-9.]+)$/) || [])[1];
-- assert.strictEqual(tag('openvibe-contracts'), 'v0.76.0');
++ assert.strictEqual(tag('openvibe-contracts'), 'v0.79.0');
assert.strictEqual(tag('openvibe-sdk'), `v${sdkPkg.version}`);
assert.strictEqual(tag('openvibe-shared'), `v${require('openvibe-shared/package.json').version}`);
-- assert.strictEqual(sdkPkg.version, '0.23.1');
-- // The contracts tag v0.76.0 carries the released codes manifest and codes.app-manifest@1.
++ assert.strictEqual(sdkPkg.version, '0.25.0');
++ // The contracts tag v0.79.0 carries the released codes manifest and codes.app-manifest@1.
assert.ok(contracts.services.get('codes') && contracts.services.get('codes').status === 'alpha');
assert.ok(contracts.resolve('codes.app-manifest@1'));
});
@@@ -33,13 +33,13 @@ const sdkPkg = require('openvibe-sdk/package.json');
const r = await t.get(p);
assert.strictEqual(r.status, 200, p);
assert.ok(r.text.includes(`openvibe-contracts v${contractsVersion}`), `${p} names the installed contracts version`);
-- assert.ok(r.text.includes('OpenVibe.Contracts/tree/v0.76.0'), `${p} links the pinned tag`);
-- if (contractsVersion !== '0.76.0') assert.ok(r.text.includes('(tag v0.76.0)'), `${p} says the tag differs from the package version`);
++ assert.ok(r.text.includes('OpenVibe.Contracts/tree/v0.79.0'), `${p} links the pinned tag`);
++ if (contractsVersion !== '0.79.0') assert.ok(r.text.includes('(tag v0.79.0)'), `${p} says the tag differs from the package version`);
assert.ok(r.text.includes(`openvibe-sdk v${sdkPkg.version}`), `${p} names the SDK version`);
}
const v = (await t.get('/api/v1/docs/versions')).json();
assert.strictEqual(v.contracts, contractsVersion);
-- assert.strictEqual(v.contracts_tag, 'v0.76.0');
++ assert.strictEqual(v.contracts_tag, 'v0.79.0');
assert.strictEqual(v.sdk, sdkPkg.version);
});