openvibers/openvibe.tips Commits

openvibe-shared v1.5.0 (Track R release manifest), openvibe-contracts v0.32.0, openvibe-sdk v0.5.0

OP OpenVibers committed 23 Sept 2026 · 9acca6a2ad · 7 files, +34 −30

- /release.json is mounted with release.mount(app, { registry }): POST /release-metrics takes open
  tabs' update reports into /metrics (release_client_updates_total), and the pages' ov-release meta
  tag carries data-metrics. On contracts 0.32.0 the manifest has the release-manifest 1.1.0 fields;
  no components are declared, so every release still prompts a reload. test/api.test.js validates
  the served manifest against the contracts.
- Contracts 0.32.0 registers tips.interaction.moderate and the tips.interaction.moderated|erased
  payload contracts Tips drafted; the moderation and privacy tests now validate against the
  released schemas (they already preferred them). README, STATUS and the v1 capability comment no
  longer call them proposals.

Changes

ddiff --git a/README.md b/README.md
iindex 7b96a07..45f35fd 100644
---- a/README.md
++++ b/README.md
@@@ -41,7 +41,7 @@ from *interaction delivered*.
(openvibe-sdk transactional outbox)
- **OpenVibe.Network** — SSO for people, service tokens, JWKS, identity resolve (importer)
- **OpenVibe.Live** — chat delivery through `/internal/tips/deliveries` (deployed in Live since `f11f809`; [docs/live-patch.diff](docs/live-patch.diff) is the original patch) until OpenVibe.Chat exposes paid messages; overlay consumer
--- **OpenVibe.Shared** v1.3.0 (app icon, SSR footer, noscript nav, release manifest, legal pages) and
++- **OpenVibe.Shared** v1.5.0 (app icon, SSR footer, noscript nav, release manifest, legal pages) and
the Network's `navbar.js`
## Run it
@@@ -121,9 +121,9 @@ token as a Bearer and act on their own things only. The API never reads cookies.
| `GET /goals?creator=`, `GET /goals/:id` | `tips.goal.update` (private; full view) | public shape when the page is on (the creator's page settings); owner sees contributions |
| `GET /profiles/:creator/supporters` | `tips.profile.get` (while not public) | public when the creator shows the supporters page; the owner |
| `GET /me/export`, `POST /me/erase` | — (people only) | the supporter's own tips: download; erase their data from them |
--| `GET /moderation?creator=&state=held\|hidden\|visible\|all` | `tips.interaction.moderate` (proposed) | the creator and their moderators |
--| `POST /interactions/:id/hide`, `POST /interactions/:id/restore` `{reason?}` | `tips.interaction.moderate` (proposed) | the creator and their moderators |
--| `GET /moderation/log?creator=` | `tips.interaction.moderate` (proposed) | owner |
++| `GET /moderation?creator=&state=held\|hidden\|visible\|all` | `tips.interaction.moderate` | the creator and their moderators |
++| `POST /interactions/:id/hide`, `POST /interactions/:id/restore` `{reason?}` | `tips.interaction.moderate` | the creator and their moderators |
++| `GET /moderation/log?creator=` | `tips.interaction.moderate` | owner |
| `GET\|POST /moderators`, `POST /moderators/:subject/remove` | `tips.profile.update` | owner |
| `POST /goals`, `PATCH /goals/:id`, `POST /goals/:id/close` | `tips.goal.create` / `.update` / `.close` | owner |
| `GET\|POST /overlay-tokens`, `POST /overlay-tokens/:id/revoke` | `tips.overlay.token.create` / `.revoke` | owner |
@@@ -131,15 +131,16 @@ token as a Bearer and act on their own things only. The API never reads cookies.
| `POST /simulate` | `tips.simulation.run` | owner |
| `POST /internal/events` | Events webhook signature (`TIPS_EVENTS_SECRET`), loopback only | — |
--`tips.interaction.moderate` is proposed in [docs/capabilities-proposal/](docs/capabilities-proposal/) and
--not in openvibe-contracts yet (grants are matched by string, so a Network grant of it works today).
++`tips.interaction.moderate` was proposed in [docs/capabilities-proposal/](docs/capabilities-proposal/)
++and is registered in openvibe-contracts v0.32.0.
The capabilities and the service manifest were released in openvibe-contracts v0.15.0 (3-segment ids:
the charter's `tips.simulate` is `tips.simulation.run`; `tips.interaction.record` is new, for EXTERNAL
tips); the drafts stay in [docs/capabilities-proposal/](docs/capabilities-proposal/) and
[docs/service-manifest-proposal.json](docs/service-manifest-proposal.json). Grants are matched with
--contracts' `capabilities.grants()` (exact id or a `.*` family). Tips pins openvibe-contracts v0.30.2,
--which also carries the payload schemas of the six `tips.*` events below; `test/contracts.test.js`
--validates every envelope and payload Tips produces against them.
++contracts' `capabilities.grants()` (exact id or a `.*` family). Tips pins openvibe-contracts v0.32.0,
++which also carries the payload schemas of the six `tips.*` events below (v0.30.2) and of
++`tips.interaction.moderated|erased` (v0.32.0); `test/contracts.test.js` validates every envelope and
++payload Tips produces against them.
## Privacy
@@@ -203,8 +204,8 @@ toward its goal and the creator's totals.
Produced through the openvibe-sdk outbox (table `event_outbox`, source `tips`, same transaction as the
change; relayed when `EVENTS_URL` and the client secret are set): `tips.interaction.ready`,
`tips.interaction.failed`, `tips.interaction.cancelled`, `tips.goal.updated`, `tips.overlay.delivered`,
--`tips.overlay.failed`, and two whose schemas are proposed in [docs/events-proposal/](docs/events-proposal/)
--(not yet in openvibe-contracts): `tips.interaction.erased` (a supporter's erasure; `{ interaction_id,
++`tips.overlay.failed`, and two whose schemas were proposed in [docs/events-proposal/](docs/events-proposal/)
++and are in openvibe-contracts since v0.32.0: `tips.interaction.erased` (a supporter's erasure; `{ interaction_id,
creator, erased_at, redacts }`) and `tips.interaction.moderated` (`{ interaction_id, creator, action:
filtered|held|hidden|restored, by: filter|creator|moderator|service, moderation_state, cancelled_effects }`).
Simulations and Billing test money produce none. An anonymous supporter is never named in an event.
@@@ -336,7 +337,7 @@ exist here (plan §12.12):
4. real persistence and end-to-end workflows — ✔ against stubs; not yet against the real Billing and
Events (Billing is in shadow and has sent no events; the production database is empty; the Live
import has not been run);
--5. capability and event registration against OpenVibe.Contracts — ✔ v0.15.0, payload schemas in v0.30.2 (pinned);
++5. capability and event registration against OpenVibe.Contracts — ✔ v0.15.0, payload schemas in v0.30.2 and v0.32.0 (pinned);
6. a migration/seed strategy ✔, a written threat review ✔ ([docs/threat-review.md](docs/threat-review.md),
internal; an independent review is still due), and sitemap/robots ✔ (no feed);
7. acceptance tests proving the advertised functionality — ✔ (table above).
ddiff --git a/STATUS.json b/STATUS.json
iindex 3a68d84..13cba96 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -7,6 +7,6 @@
"adr": "ADR-012",
"authoritative": false,
"deployment": "Deployed internally, not launched: openvibe-ovh /opt/openvibe.tips (release 435f32b), unit openvibe-tips, 127.0.0.1:4610 loopback only; https://openvibe.tips still serves the OpenVibe.Sites placeholder. Principal tips; Events subscription billing.transaction.* -> /internal/events; TIPS_CHAT_ADAPTER=none. Pins openvibe-contracts v0.15.0, openvibe-sdk v0.4.0, openvibe-shared v1.2.1.",
-- "notes": "Runtime, API, SSR pages, overlays, Billing settlement consumer, events outbox, Live import and reconciliation are built and tested against stubs. Capabilities (17 tips.*) and the service manifest are registered in OpenVibe.Contracts v0.15.0; the six tips.* event payload schemas are in v0.30.2, which the repository pins since 2026-09-23 (production still runs v0.15.0). Production database is empty (0 profiles, interactions, goals, overlay tokens); the Live import (scripts/import-live.js) has never been run; 0 billing events have ever arrived because Billing runs in shadow. Live's /internal/tips/deliveries route is deployed (Live f11f809) but never called. The restore drill passed on the host 2026-09-23 05:23 UTC. Built since (not deployed yet): /metrics (loopback only) and the Billing/Events checks in /api/ready. Built since (not deployed yet): supporter privacy (anonymous, hidden amount, private message) across API, overlays, chat jobs, pages and events; the creator's public goal and supporters page settings; a supporter's export and erasure (tips.interaction.erased, schema proposed in docs/events-proposal/, not yet in openvibe-contracts). Moderation (not deployed yet): the creator's word filter (mask or hold for review) before anything is shown or read, hide/show by the creator, moderators (invitation links) or services, tips.interaction.moderated (schema and the tips.interaction.moderate capability proposed in docs/, not yet in openvibe-contracts). A written internal threat review (docs/threat-review.md) with its fixes and test/security.test.js. Not done: an independent security review; the openvibe.tips launch. Live stays the place tips happen until the Billing cutover (blocked on the owner re-pointing the PowerChat webhook) and the launch rule in README.md holds.",
++ "notes": "Runtime, API, SSR pages, overlays, Billing settlement consumer, events outbox, Live import and reconciliation are built and tested against stubs. Capabilities (17 tips.*) and the service manifest are registered in OpenVibe.Contracts v0.15.0; the six tips.* event payload schemas are in v0.30.2 and tips.interaction.moderated|erased in v0.32.0, which the repository pins since 2026-09-23 (production still runs v0.15.0). Production database is empty (0 profiles, interactions, goals, overlay tokens); the Live import (scripts/import-live.js) has never been run; 0 billing events have ever arrived because Billing runs in shadow. Live's /internal/tips/deliveries route is deployed (Live f11f809) but never called. The restore drill passed on the host 2026-09-23 05:23 UTC. Built since (not deployed yet): /metrics (loopback only) and the Billing/Events checks in /api/ready. Built since (not deployed yet): supporter privacy (anonymous, hidden amount, private message) across API, overlays, chat jobs, pages and events; the creator's public goal and supporters page settings; a supporter's export and erasure (tips.interaction.erased, schema in openvibe-contracts v0.32.0). Moderation (not deployed yet): the creator's word filter (mask or hold for review) before anything is shown or read, hide/show by the creator, moderators (invitation links) or services, tips.interaction.moderated (schema and the tips.interaction.moderate capability in openvibe-contracts v0.32.0). A written internal threat review (docs/threat-review.md) with its fixes and test/security.test.js. Not done: an independent security review; the openvibe.tips launch. Live stays the place tips happen until the Billing cutover (blocked on the owner re-pointing the PowerChat webhook) and the launch rule in README.md holds.",
"updated": "2026-09-23"
}
ddiff --git a/package-lock.json b/package-lock.json
iindex 91f8318..e8f8d28 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -14,9 +14,9 @@
"dotenv": "^16.4.7",
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.30.2",
-- "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-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.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"
},
"engines": {
"node": ">=22.0.0"
@@@ -983,9 +983,9 @@
}
},
"node_modules/openvibe-contracts": {
-- "version": "0.30.2",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.30.2",
-- "integrity": "sha512-R01NgRY5V/GEYWkWUyxfl1MifSQb4dfJy11VS9RP+wsWedh2Crr9/IL7yGSn1PytwTKKL+tQl/r4ppGfH3TwLg==",
++ "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",
@@@ -999,18 +999,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"
ddiff --git a/package.json b/package.json
iindex d9a18b0..cb29970 100644
---- a/package.json
++++ b/package.json
@@@ -21,9 +21,9 @@
"dotenv": "^16.4.7",
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
-- "openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.30.2",
-- "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-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.32.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"
},
"engines": {
"node": ">=22.0.0"
ddiff --git a/server/api/v1.js b/server/api/v1.js
iindex 28205fd..72d318d 100644
---- a/server/api/v1.js
++++ b/server/api/v1.js
@@@ -61,7 +61,7 @@ const CAP = {
configGet: 'tips.overlay.config.get',
configUpdate: 'tips.overlay.config.update',
simulate: 'tips.simulation.run',
-- moderate: 'tips.interaction.moderate', // proposed (docs/capabilities-proposal/), not yet in openvibe-contracts
++ moderate: 'tips.interaction.moderate', // in openvibe-contracts since v0.32.0 (was docs/capabilities-proposal/)
};
function v1Router({ domain, apiAuth }) {
ddiff --git a/server/app.js b/server/app.js
iindex b905ea2..d01df58 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -82,7 +82,8 @@ function createApp(opts = {}) {
// Events are optional and degrade it (see observability.js).
const readiness = createTipsReadiness({ db, keys, config, outbox, release: release.release, fetchImpl });
app.get('/api/ready', readiness.handler);
-- app.get('/release.json', release.handler);
++ // GET /release.json (ADR-016) and POST /release-metrics: open tabs' update reports into /metrics.
++ release.mount(app, { registry: metrics.registry });
const consumer = consumerRouter({ domain, config, log });
// Service-to-service only: OpenVibe.Events calls 127.0.0.1:4610 directly; anything that came
ddiff --git a/test/api.test.js b/test/api.test.js
iindex a0b5923..5d13cad 100644
---- a/test/api.test.js
++++ b/test/api.test.js
@@@ -19,6 +19,8 @@ const { boot, check, done } = require('./helpers/app');
assert.strictEqual(r.json.ready, true);
const rel = await t.call('GET', '/release.json', { token: null });
assert.strictEqual(rel.json.service, 'tips');
++ assert.deepStrictEqual(require('openvibe-contracts').validate('registry.release-manifest@1', rel.json).errors, []);
++ assert.strictEqual(rel.json.metrics_url, '/release-metrics');
});
await check('a service token for another audience, or without the capability, is refused', async () => {