openvibers/openvibe.trade Commits
openvibe-shared v1.5.0 (Track R release manifest), openvibe-contracts v0.32.0, openvibe-sdk v0.5.0
Changes
@@@ -126,7 +126,7 @@ deployment has no instruments (editors add them), no documents until the source| `/robots.txt`, `/llms.txt` | crawler policy (private paths disallowed) and orientation || `/terms`, `/privacy`, `/dmca` | openvibe-shared legal pages || `/auth/*` | sign-in; the same session layer as Blog and Community |--| `/api/health`, `/api/ready`, `/release.json`, `/metrics` | health, readiness, release, metrics (loopback only) |++| `/api/health`, `/api/ready`, `/release.json`, `POST /release-metrics`, `/metrics` | health, readiness, release, open tabs' update reports (counted in `/metrics`), metrics (loopback only) || `POST /internal/events` | signed OpenVibe.Events deliveries (`sources.*`), host-local |### Discoverability (roadmap §32)@@@ -196,9 +196,9 @@ webhook, as a wake-up for the cursor sync (the webhook is not durable truth; the## Depends on--- **Packages** (pinned by release tarball): `openvibe-contracts` v0.22.0, `openvibe-publishing`-- v0.2.1 (revisions, authorship, seo, index-hooks, ssr), `openvibe-shared` v1.3.0 (chrome, app icon,-- footer, legal, release, metrics, ready), `openvibe-sdk` v0.4.0 (events outbox and inbox, webhook++- **Packages** (pinned by release tarball): `openvibe-contracts` v0.32.0, `openvibe-publishing`++ v0.2.1 (revisions, authorship, seo, index-hooks, ssr), `openvibe-shared` v1.5.0 (chrome, app icon,++ footer, legal, release, metrics, ready), `openvibe-sdk` v0.5.0 (events outbox and inbox, webhooksignatures v2, service tokens).- **OpenVibe.Network:** SSO (OAuth client `trade`, redirect `https://openvibe.trade/auth/callback`),JWKS, client-credentials tokens.@@@ -9,10 +9,10 @@"domain": "openvibe.trade","scope": "informational only (ADR-025): no custody, no order execution, no escrow, no listings between users, no personalised advice","packages": [-- "openvibe-contracts v0.22.0",++ "openvibe-contracts v0.32.0","openvibe-publishing v0.2.1",-- "openvibe-shared v1.3.0",-- "openvibe-sdk v0.4.0"++ "openvibe-shared v1.5.0",++ "openvibe-sdk v0.5.0"],"capabilities": ["trade.instrument.resolve",@@@ -15,10 +15,10 @@"express": "^4.21.2","express-rate-limit": "^7.5.0","helmet": "^8.1.0",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.22.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0","openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.2.1",-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.4.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.0"},"devDependencies": {"jsonwebtoken": "^9.0.2"@@@ -1015,9 +1015,9 @@}},"node_modules/openvibe-contracts": {-- "version": "0.22.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.22.0",-- "integrity": "sha512-+ovUsSEPT3FyUX3IMURZm0KUXeP05ZW0oOG6pTOanWhu8vc+q5B+tNnDQ7tlfyLObQ8G6phN4DSck/Ed9kfI2g==",++ "version": "0.32.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0",++ "integrity": "sha512-ntN58SIG03YDxbhsiIzOM5GRH19jRMHZUYWI3xU2VwovExagF5DdwApLVfezsu84UTJ+V2Jfn+LWDc+Oz8ab+g==","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -1060,18 +1060,18 @@}},"node_modules/openvibe-sdk": {-- "version": "0.4.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.4.0",-- "integrity": "sha512-Hyz55YOMIs0xwCQ1NXsN0sUYhlbKXAX3uWY2fPCdEEL18FXUYVhK8wlG/5JMMypFHnjDiSd9VHzBNG12hWsmkQ==",++ "version": "0.5.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",++ "integrity": "sha512-QVJkR5iazQX0ZwIrDp2A88ZeYYBSXEmSzhpgURPUSnzezIM3jz0HBhVKowyhkvg2aBO61XNf77eb4zJGkMjZiw==","license": "MIT","engines": {"node": ">=20"}},"node_modules/openvibe-shared": {-- "version": "1.3.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0",-- "integrity": "sha512-w8yl/5+9eend8I2O/DzAJnEAL2N3GTUY7c7pzK+MLJuLAGD8FmJJQ9gwbgyrq70Snjt3ebrg3TlpORhRAp2kgA==",++ "version": "1.5.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.0",++ "integrity": "sha512-cehnscuYMkJwuCYGcI6wPSswbUn0bhTyGAaD5feNCoO//2e9gyC7cgjkEcIOtM8DQF3yTuUNyXnOwckIeM4fUg==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -20,10 +20,10 @@"express": "^4.21.2","express-rate-limit": "^7.5.0","helmet": "^8.1.0",-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.22.0",++ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0","openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.2.1",-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.4.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0"++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.0"},"devDependencies": {"jsonwebtoken": "^9.0.2"@@@ -84,7 +84,8 @@ function createApp(opts = {}) {const app = express();app.disable('x-powered-by');app.set('trust proxy', config.trustProxy);-- const release = require('openvibe-shared/release').createRelease({ service: 'trade', root: path.join(__dirname, '..') });++ // metricsPath: open tabs report their update outcomes to POST /release-metrics (defined below).++ const release = require('openvibe-shared/release').createRelease({ service: 'trade', root: path.join(__dirname, '..'), metricsPath: '/release-metrics' });const metrics = require('openvibe-shared/metrics').instrument(app, { service: 'trade', release: release.release });app.locals.metrics = metrics.registry;app.locals.ctx = ctx;@@@ -116,7 +117,10 @@ function createApp(opts = {}) {// ── Machine endpoints ───────────────────────────────────const machine = express.Router();define(machine, 'get', '/api/health', 'health', (_req, res) => res.json({ status: 'ok', service: 'openvibe-trade', version: VERSION }));++ // What release.mount(app, { registry }) registers, through define() like every Trade route:++ // GET /release.json (ADR-016) and POST /release-metrics (release_client_updates_total in /metrics).define(machine, 'get', '/release.json', 'releaseInfo', release.handler);++ define(machine, 'post', '/release-metrics', 'releaseMetrics', release.collect(metrics.registry));const readiness = createTradeReadiness({ store, auth: ctx.auth, outbox: ctx.outbox, sync: ctx.sync, freshness: ctx.freshness, release: release.release });define(machine, 'get', '/api/ready', 'readiness', readiness.handler);app.use(machine);@@@ -52,8 +52,10 @@ async function startNetwork() {return json(404, { error: 'not found' });});issuer = srv.url;++ // An app token carries its developer project and env, as Network's do (identity.service-token-claims 1.2.0).function signService({ sub, aud, cap, actorType = 'service', extra = {} }) {-- return serviceAuth.signServiceToken({ iss: issuer, sub, actor_type: actorType, aud, cap, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 300, jti: crypto.randomUUID(), ...extra }, privatePem);++ const app = actorType === 'app' ? { project_id: 'prj_01J8ZQ4Y7N3M2K1H0G9F8E7D6C', env: 'production' } : {};++ return serviceAuth.signServiceToken({ iss: issuer, sub, actor_type: actorType, aud, cap, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 300, jti: crypto.randomUUID(), ...app, ...extra }, privatePem);}function addUser(username, extra = {}) {return { subject: ids.newId('user'), username, display_name: extra.display_name || username, role: extra.role || 'user' };@@@ -89,7 +89,13 @@ const DISCLAIMER = 'Information only — not investment advice; no trading here.assert.strictEqual(ready.status, 200);const body = JSON.stringify(ready.json());assert.ok(body.includes('relay off'), 'the events relay is reported off in tests');-- assert.strictEqual((await t.get('/release.json')).status, 200);++ const rel = await t.get('/release.json');++ assert.strictEqual(rel.status, 200);++ assert.deepStrictEqual(require('openvibe-contracts').validate('registry.release-manifest@1', rel.json()).errors, []);++ assert.strictEqual(rel.json().metrics_url, '/release-metrics');++ const report = await t.get('/release-metrics', { method: 'POST', headers: { 'content-type': 'text/plain' }, body: JSON.stringify({ counts: { applied: { style: 1 } } }) });++ assert.strictEqual(report.status, 204);++ assert.ok((await t.get('/metrics')).text.includes('release_client_updates_total{outcome="applied",reason="style"} 1\n'));const terms = await t.get('/terms');assert.strictEqual(terms.status, 200);assert.ok(terms.text.includes('OpenVibe.Trade'));
@@ -126,7 +126,7 @@ deployment has no instruments (editors add them), no documents until the source
@@ -126,7 +126,7 @@ deployment has no instruments (editors add them), no documents until the source
| `/robots.txt`, `/llms.txt` | crawler policy (private paths disallowed) and orientation |
| `/robots.txt`, `/llms.txt` | crawler policy (private paths disallowed) and orientation |
| `/terms`, `/privacy`, `/dmca` | openvibe-shared legal pages |
| `/terms`, `/privacy`, `/dmca` | openvibe-shared legal pages |
| `/auth/*` | sign-in; the same session layer as Blog and Community |
| `/auth/*` | sign-in; the same session layer as Blog and Community |
-| `/api/health`, `/api/ready`, `/release.json`, `/metrics` | health, readiness, release, metrics (loopback only) |
+| `/api/health`, `/api/ready`, `/release.json`, `POST /release-metrics`, `/metrics` | health, readiness, release, open tabs' update reports (counted in `/metrics`), metrics (loopback only) |
| `POST /internal/events` | signed OpenVibe.Events deliveries (`sources.*`), host-local |
| `POST /internal/events` | signed OpenVibe.Events deliveries (`sources.*`), host-local |
### Discoverability (roadmap §32)
### Discoverability (roadmap §32)
@@ -196,9 +196,9 @@ webhook, as a wake-up for the cursor sync (the webhook is not durable truth; the
@@ -196,9 +196,9 @@ webhook, as a wake-up for the cursor sync (the webhook is not durable truth; the
## Depends on
## Depends on
-- **Packages** (pinned by release tarball): `openvibe-contracts` v0.22.0, `openvibe-publishing`
- v0.2.1 (revisions, authorship, seo, index-hooks, ssr), `openvibe-shared` v1.3.0 (chrome, app icon,
- footer, legal, release, metrics, ready), `openvibe-sdk` v0.4.0 (events outbox and inbox, webhook
+- **Packages** (pinned by release tarball): `openvibe-contracts` v0.32.0, `openvibe-publishing`
+ v0.2.1 (revisions, authorship, seo, index-hooks, ssr), `openvibe-shared` v1.5.0 (chrome, app icon,
+ footer, legal, release, metrics, ready), `openvibe-sdk` v0.5.0 (events outbox and inbox, webhook
signatures v2, service tokens).
signatures v2, service tokens).
- **OpenVibe.Network:** SSO (OAuth client `trade`, redirect `https://openvibe.trade/auth/callback`),
- **OpenVibe.Network:** SSO (OAuth client `trade`, redirect `https://openvibe.trade/auth/callback`),
JWKS, client-credentials tokens.
JWKS, client-credentials tokens.
@@ -9,10 +9,10 @@
@@ -9,10 +9,10 @@
"domain": "openvibe.trade",
"domain": "openvibe.trade",
"scope": "informational only (ADR-025): no custody, no order execution, no escrow, no listings between users, no personalised advice",
"scope": "informational only (ADR-025): no custody, no order execution, no escrow, no listings between users, no personalised advice",
"packages": [
"packages": [
- "openvibe-contracts v0.22.0",
+ "openvibe-contracts v0.32.0",
"openvibe-publishing v0.2.1",
"openvibe-publishing v0.2.1",
- "openvibe-shared v1.3.0",
- "openvibe-sdk v0.4.0"
+ "openvibe-shared v1.5.0",
+ "openvibe-sdk v0.5.0"
],
],
"capabilities": [
"capabilities": [
"trade.instrument.resolve",
"trade.instrument.resolve",
@@ -15,10 +15,10 @@
@@ -15,10 +15,10 @@
"express": "^4.21.2",
"express": "^4.21.2",
"express-rate-limit": "^7.5.0",
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"helmet": "^8.1.0",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.22.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.2.1",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.2.1",
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.4.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.0"
},
},
"devDependencies": {
"devDependencies": {
"jsonwebtoken": "^9.0.2"
"jsonwebtoken": "^9.0.2"
@@ -1015,9 +1015,9 @@
@@ -1015,9 +1015,9 @@
}
}
},
},
"node_modules/openvibe-contracts": {
"node_modules/openvibe-contracts": {
- "version": "0.22.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.22.0",
- "integrity": "sha512-+ovUsSEPT3FyUX3IMURZm0KUXeP05ZW0oOG6pTOanWhu8vc+q5B+tNnDQ7tlfyLObQ8G6phN4DSck/Ed9kfI2g==",
+ "version": "0.32.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0",
+ "integrity": "sha512-ntN58SIG03YDxbhsiIzOM5GRH19jRMHZUYWI3xU2VwovExagF5DdwApLVfezsu84UTJ+V2Jfn+LWDc+Oz8ab+g==",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -1060,18 +1060,18 @@
@@ -1060,18 +1060,18 @@
}
}
},
},
"node_modules/openvibe-sdk": {
"node_modules/openvibe-sdk": {
- "version": "0.4.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.4.0",
- "integrity": "sha512-Hyz55YOMIs0xwCQ1NXsN0sUYhlbKXAX3uWY2fPCdEEL18FXUYVhK8wlG/5JMMypFHnjDiSd9VHzBNG12hWsmkQ==",
+ "version": "0.5.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
+ "integrity": "sha512-QVJkR5iazQX0ZwIrDp2A88ZeYYBSXEmSzhpgURPUSnzezIM3jz0HBhVKowyhkvg2aBO61XNf77eb4zJGkMjZiw==",
"license": "MIT",
"license": "MIT",
"engines": {
"engines": {
"node": ">=20"
"node": ">=20"
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "1.3.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0",
- "integrity": "sha512-w8yl/5+9eend8I2O/DzAJnEAL2N3GTUY7c7pzK+MLJuLAGD8FmJJQ9gwbgyrq70Snjt3ebrg3TlpORhRAp2kgA==",
+ "version": "1.5.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.0",
+ "integrity": "sha512-cehnscuYMkJwuCYGcI6wPSswbUn0bhTyGAaD5feNCoO//2e9gyC7cgjkEcIOtM8DQF3yTuUNyXnOwckIeM4fUg==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -20,10 +20,10 @@
@@ -20,10 +20,10 @@
"express": "^4.21.2",
"express": "^4.21.2",
"express-rate-limit": "^7.5.0",
"express-rate-limit": "^7.5.0",
"helmet": "^8.1.0",
"helmet": "^8.1.0",
- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.22.0",
+ "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.2.1",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v0.2.1",
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.4.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.3.0"
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.5.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.5.0"
},
},
"devDependencies": {
"devDependencies": {
"jsonwebtoken": "^9.0.2"
"jsonwebtoken": "^9.0.2"
@@ -84,7 +84,8 @@ function createApp(opts = {}) {
@@ -84,7 +84,8 @@ function createApp(opts = {}) {
const app = express();
const app = express();
app.disable('x-powered-by');
app.disable('x-powered-by');
app.set('trust proxy', config.trustProxy);
app.set('trust proxy', config.trustProxy);
- const release = require('openvibe-shared/release').createRelease({ service: 'trade', root: path.join(__dirname, '..') });
+ // metricsPath: open tabs report their update outcomes to POST /release-metrics (defined below).
+ const release = require('openvibe-shared/release').createRelease({ service: 'trade', root: path.join(__dirname, '..'), metricsPath: '/release-metrics' });
const metrics = require('openvibe-shared/metrics').instrument(app, { service: 'trade', release: release.release });
const metrics = require('openvibe-shared/metrics').instrument(app, { service: 'trade', release: release.release });
app.locals.metrics = metrics.registry;
app.locals.metrics = metrics.registry;
app.locals.ctx = ctx;
app.locals.ctx = ctx;
@@ -116,7 +117,10 @@ function createApp(opts = {}) {
@@ -116,7 +117,10 @@ function createApp(opts = {}) {
// ── Machine endpoints ───────────────────────────────────
// ── Machine endpoints ───────────────────────────────────
const machine = express.Router();
const machine = express.Router();
define(machine, 'get', '/api/health', 'health', (_req, res) => res.json({ status: 'ok', service: 'openvibe-trade', version: VERSION }));
define(machine, 'get', '/api/health', 'health', (_req, res) => res.json({ status: 'ok', service: 'openvibe-trade', version: VERSION }));
+ // What release.mount(app, { registry }) registers, through define() like every Trade route:
+ // GET /release.json (ADR-016) and POST /release-metrics (release_client_updates_total in /metrics).
define(machine, 'get', '/release.json', 'releaseInfo', release.handler);
define(machine, 'get', '/release.json', 'releaseInfo', release.handler);
+ define(machine, 'post', '/release-metrics', 'releaseMetrics', release.collect(metrics.registry));
const readiness = createTradeReadiness({ store, auth: ctx.auth, outbox: ctx.outbox, sync: ctx.sync, freshness: ctx.freshness, release: release.release });
const readiness = createTradeReadiness({ store, auth: ctx.auth, outbox: ctx.outbox, sync: ctx.sync, freshness: ctx.freshness, release: release.release });
define(machine, 'get', '/api/ready', 'readiness', readiness.handler);
define(machine, 'get', '/api/ready', 'readiness', readiness.handler);
app.use(machine);
app.use(machine);
@@ -52,8 +52,10 @@ async function startNetwork() {
@@ -52,8 +52,10 @@ async function startNetwork() {
return json(404, { error: 'not found' });
return json(404, { error: 'not found' });
});
});
issuer = srv.url;
issuer = srv.url;
+ // An app token carries its developer project and env, as Network's do (identity.service-token-claims 1.2.0).
function signService({ sub, aud, cap, actorType = 'service', extra = {} }) {
function signService({ sub, aud, cap, actorType = 'service', extra = {} }) {
- return serviceAuth.signServiceToken({ iss: issuer, sub, actor_type: actorType, aud, cap, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 300, jti: crypto.randomUUID(), ...extra }, privatePem);
+ const app = actorType === 'app' ? { project_id: 'prj_01J8ZQ4Y7N3M2K1H0G9F8E7D6C', env: 'production' } : {};
+ return serviceAuth.signServiceToken({ iss: issuer, sub, actor_type: actorType, aud, cap, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 300, jti: crypto.randomUUID(), ...app, ...extra }, privatePem);
}
}
function addUser(username, extra = {}) {
function addUser(username, extra = {}) {
return { subject: ids.newId('user'), username, display_name: extra.display_name || username, role: extra.role || 'user' };
return { subject: ids.newId('user'), username, display_name: extra.display_name || username, role: extra.role || 'user' };
@@ -89,7 +89,13 @@ const DISCLAIMER = 'Information only — not investment advice; no trading here.
@@ -89,7 +89,13 @@ const DISCLAIMER = 'Information only — not investment advice; no trading here.
assert.strictEqual(ready.status, 200);
assert.strictEqual(ready.status, 200);
const body = JSON.stringify(ready.json());
const body = JSON.stringify(ready.json());
assert.ok(body.includes('relay off'), 'the events relay is reported off in tests');
assert.ok(body.includes('relay off'), 'the events relay is reported off in tests');
- assert.strictEqual((await t.get('/release.json')).status, 200);
+ const rel = await t.get('/release.json');
+ assert.strictEqual(rel.status, 200);
+ assert.deepStrictEqual(require('openvibe-contracts').validate('registry.release-manifest@1', rel.json()).errors, []);
+ assert.strictEqual(rel.json().metrics_url, '/release-metrics');
+ const report = await t.get('/release-metrics', { method: 'POST', headers: { 'content-type': 'text/plain' }, body: JSON.stringify({ counts: { applied: { style: 1 } } }) });
+ assert.strictEqual(report.status, 204);
+ assert.ok((await t.get('/metrics')).text.includes('release_client_updates_total{outcome="applied",reason="style"} 1\n'));
const terms = await t.get('/terms');
const terms = await t.get('/terms');
assert.strictEqual(terms.status, 200);
assert.strictEqual(terms.status, 200);
assert.ok(terms.text.includes('OpenVibe.Trade'));
assert.ok(terms.text.includes('OpenVibe.Trade'));