Commits
-
T2 cutover: runbook and rehearsal for the PostgreSQL switch (#11)OpenVibers · 2 Oct 2026
235f13f3 -
Platform plan T2 "Follows", job 1 of 2: Network resolves go-live… (#6)OpenVibers · 2 Oct 2026
e6b426cb -
Pins: openvibe-shared v2.3.1, openvibe-sdk v0.25.2, openvibe-contracts v0.83.0 (package.json; lockfile regenerated locally)OpenVibers · 29 Sept 2026
bce46e67 -
T2 Network on PostgreSQL, work in progress: what eight DeepSeek attempts left in the working tree (asyncified call sites, dialect fixes, migrations 0003-0006, test fixes); unfinished, continued on this branch by a cloud session.OpenVibers · 29 Sept 2026
206ab360 -
Network on PostgreSQL, the foundation (plan T2): openvibe-sdk v0.25.1 with pg, iovalkey and PGlite; migrations 0001_core (69 tables generated from the booted SQLite schema: identities, text timestamps via ov_now(), case-insensitive usernames by a unique lower(username) index, triggers in PL/pgSQL, boot seeds left to the code) and 0002_analytics; DATABASE_URL/DATABASE_DIRECT_URL/PGLITE_DIR/VALKEY_URL config; scripts/migrate-to-postgres.js (import with per-table count and checksum, --pglite dry run) and scripts/pg-preflight-collisions.js; the per-process migrated test database runner (not wired into npm test until the code is converted). Additive: the service still runs on SQLite and npm test is unchanged.OpenVibers · 29 Sept 2026
c114c402 -
Grants for plan T3 and T4, on Contracts 0.80.0: Live reads a channel's moderation settings, moderators and emote count from Chat (chat.moderation.read); Chat uploads and deletes emote images in Media's chat namespace and runs the chat-AI summaries with its own token (ai.run.create/read, chat.*); Live reads, lists, uploads and deletes its objects in Media's live namespace and OpenRe uploads there, so both drop Media API keys. principals.test.js pins Live's Media scope. npm test green.OpenVibers · 29 Sept 2026
b5d87073 -
Plan T2 deletions in Network (each proven uncalled across the estate first): the one-time scripts hash-refresh-tokens, follows-backfill and creator-analytics-backfill; the built-in fallback tool catalog (before Tools has answered, the catalog is empty); the legacy OIDC alias /oauth/.well-known/openid-configuration (the issuer-root discovery and RFC 8414 stay); docs/patches; the Wave 0 roadmap baseline (scripts, docs, test), superseded by the plan; every README/docs mention of the retired X-Internal-Key (docs/retirement.md now records its retirement). Kept, with their callers: scripts/import-blocks.js (Chat's DM-blocks migration runs it) and scripts/secrets-out-of-db.js (no record that it ran). With an empty catalog the home page says 'Tools that just open' and 'free online tools' instead of '0', shows no empty rows, and llms.txt leaves the empty section out (test/home-empty-catalog.test.js). npm test green.OpenVibers · 29 Sept 2026
f6fe8570 -
X-Internal-Key retirement, Network's step (plan T2, register C-50–C-58), on openvibe-contracts v0.79.0 (via v0.78.0: the realtime manifest is gone, so the registry tests assert its absence). The 13 key-only internal routes nothing in the estate calls are deleted (verify-token, users/:id, users/by-username, users/bulk, users/:id/theme, users/:id/linked-accounts, audit, stats, notifications/unread/:userId, notifications/resolve-users, issue-token, anon-admin, anon-list). The two Live still called with the key take its token: POST /internal/user-avatar (network.avatar.write) and POST /internal/notifications/mark-read (network.notifications.push); Live is granted network.avatar.write, network.registry.read and network.coins.read. Network's own calls carry its self-signed service token (identity/self-token.js, one cache per audience and capability set): the avatar push to Live (live.avatar.write), avatar import on Media (media.avatar.ingest), Live's analytics totals (live.analytics.read), Live's registry refresh (live.url_registry.refresh), the Tools analytics (tools.analytics.read); the key rides along only while those receivers move. Games and Media never had /api/internal/analytics: the dashboard uses the navbar page-view count for them without asking. npm test green.OpenVibers · 29 Sept 2026
79a4eea2 -
openvibe-shared v1.29.2 (from its earlier pin): navbar dropdowns are no longer clipped by the links row, changing the theme repaints the page background, bright accents get dark button text (WCAG), the navbar brand is more compact. npm test: deploy wrapper: all checks passedOpenVibers · 29 Sept 2026
5ca28e2d -
openvibe-shared v1.29.1 (from its earlier pin): changing the theme repaints the page background, bright accents get dark button text (WCAG), the navbar brand is more compact. Account page: primary buttons use the theme's on-accent colour, the theme switcher applies through the shared loader (canvas and browser UI follow), and every text field follows the theme (input colour, hover tint, accent focus ring, placeholder, invalid state); the theme submission form uses the page's field style with a hint. npm test: deploy wrapper: all checks passedOpenVibers · 29 Sept 2026
60f5ab2b -
openvibe-shared v1.28.1 (from its earlier pin): the account panel keeps its sections' height on phones, the codes mark at rest shows its V, no empty space under the footer on phones. Images may come from openvibe.media (img-src): avatars and media live there, and the Network site's own CSP blocked them. npm test: deploy wrapper: all checks passedOpenVibers · 28 Sept 2026
768e0856 -
The node registry (roadmap WS-X1 step 3, ADR-034 §12; Contracts v0.76.0 network.node@1). POST /internal/nodes/report takes Host's token only (network.node.report, granted to host; no shared key) and network.node-report-request@1: it upserts every node of that source and marks the source's nodes missing from the report down, never deleting them. GET /api/v1/nodes is public (CORS *, max-age 60) and filterable by ?role= and ?region=, answering network.node-list-result@1; it is what openvibe-sdk/geo reads. GET /api/v1/nodes/:id/beacon is a no-store 204 with Timing-Allow-Origin for browser distance timing. The contract has no address field, so nothing private is stored. openvibe-contracts pinned at v0.76.0. test/nodes.test.js covers who may report, the contract, down-marking per source, the filters and the beacon.OpenVibers · 28 Sept 2026
719a00d2 -
No fake green in Network's readiness and in the registry's status (roadmap WS-Q task 7; Shared v1.24.0). Production's /api/ready reported the Discord bot check "ok" with the detail "not configured"; it now returns skip('not configured'), so it reads skipped with its reason and never ok (an optional skip leaves the verified service ready). The registry (/api/v1/status, the registry's runtime rows) called three things "up" that verified nothing: a readiness body not in the openvibe-shared/ready shape (OpenRe's, basis ready-legacy: HTTP 200 alone), a liveness-only answer (basis health), and a shared-shape body whose checks all skipped or that had none. Each is now degraded with the reason; up needs a shared-shape body that says ready, degrades nothing and has at least one check with status ok, whatever Shared version wrote it. The row's ready summary keeps a skipped check as skipped (with its reason) instead of fail, and lists skipped. openvibe-shared is pinned at v1.24.0 (from v1.22.0). Tests: observability (no bot token: skipped, still ready), status (liveness only and the ad-hoc body are degraded; skip beside a pass stays up; all skipped or none is degraded), registry-ecosystem (its stand-in service now answers a real readiness body). npm test passes.OpenVibers · 28 Sept 2026
ac71bb4d -
Network notifies a new follow itself (roadmap WS-E task 3, D08: Live's pushes that have a domain event move off Live; ADR-020, ADR-030). Since follows moved to Network (FOLLOWS_AUTHORITY=network, 2026-09-26), a follow made on my.openvibe.network, another product or the API told nobody, and one made on Live was pushed by Live over the internal notification route. setFollow now calls a notifier (follows.setNotifier, set at boot to server/notifications/follow-notify.js) when a follow starts, inside its transaction, so the follow, its network.follow.created event, the FOLLOW notification and its network.notification.created event commit together; a notifier that throws is logged and never fails the follow. Flag changes, the same follow again, unfollows and imports (emit: false) notify nobody. The notification matches Live's (New Follower, "<name> followed you", the follower's channel at openvibe.live/@<username>, service live, the follower as sender) and goes through the notification service, so the social preference, blocks (actor_subject) and push apply. FOLLOW now shares the go-live dedupe: one an hour per sender, so unfollowing and following again does not notify twice (and Live's push, which names the same Network sender, is absorbed until Live stops sending it). test/follow-notify.test.js covers all of that; blocks.test.js's "unblocked" check uses a MENTION, since a second FOLLOW from the same person within the hour is now deduped. npm test passes.OpenVibers · 28 Sept 2026
8188c26f -
Per-actor limits on the account API's writes (roadmap WS-R task 4; openvibe-sdk 0.11.0 -> 0.12.0 openvibe-sdk/limits). server/auth/actor-limits.js counts writes by the person whose session token makes them (their subject).OpenVibers · 28 Sept 2026
ddd87b68 -
Live may cap a streamer's AI-viewer spend as an AI quota on their attribution (ai.quota.attribution.manage on openvibe.ai; roadmap WS-O task 2, AI 960f14a; openvibe-contracts 0.73.0 -> 0.75.0). test/principals.test.js follows Live's AI caps. npm test passes.OpenVibers · 28 Sept 2026
728266be -
Live may store a streamer's own provider key in OpenVibe.AI (ai.credential.manage on openvibe.ai; roadmap WS-O task 2, AI fca04c7; openvibe-contracts 0.72.0 -> 0.73.0), so Live stops keeping third-party keys in its database. test/principals.test.js follows Live's AI caps. npm test passes.OpenVibers · 28 Sept 2026
d37ee87f -
Mod principals (roadmap WS-M task 3, ADR-013; openvibe-contracts 0.71.0 -> 0.72.0). server/identity/mod-principals.js: a mod install is the principal mod:<mod_id>, whose grants are the approved subset of what its manifest requests.OpenVibers · 27 Sept 2026
c3a64415 -
Account export and deletion (roadmap WS-B task 7, ADR-033; openvibe-contracts 0.70.0 -> 0.71.0). server/identity/account-data.js.OpenVibers · 27 Sept 2026
e2787eb4 -
Account merge (roadmap WS-B task 5, ADR-029; openvibe-contracts 0.68.0 -> 0.70.0). server/identity/account-merge.js folds a second account into the one a person keeps. The person: the account they keep opens a 10-minute intent (POST /api/v1/account/merge/intents); then, freshly signed in to the other account (auth_time within 10 minutes: a real sign-in, never a renewal), POST /api/v1/account/merge { intent }. Staff, for account recovery: POST /api/admin/account-merges { from, into, reason }, which needs staff.identity.merge (the owner), a written reason and an audit row. One transaction moves what Network owns: linked providers (a clashing one stays on the folded account and still signs in to the survivor), sessions, OAuth tokens and codes, developer projects and memberships, OpenCoins (debit and credit with reason account_merge, idempotent by merge id), user modules (the survivor's fields win; the other fills only what it lacks, network.module.updated subject_merged), follows both ways (a clash or a self-follow is dropped), blocks, notifications, preferences, push subscriptions and effects. It writes the alias (subject_aliases, kept flat), marks the folded account merged_into, revokes its tokens (network.user.token_valid_after, reason account_merged) and queues network.subject.merged. A retry answers the first merge. subjects.resolve answers the survivor, with merged_from, for the alias and any legacy id mapped to it (the legacy map is never repointed). Password sign-in to a folded account signs in to the survivor. Merge records drop their pre-merge state after 30 days (a daily reduce). User session tokens carry auth_time (OIDC): set at sign-in, and every renewal (requireAuth, refresh, OAuth authorize, themes, add-account sessions, the upstream re-mint) keeps the original or none; requireAuth exposes req.tokenClaims. my.html: a "Merge two accounts" card in Your Accounts runs the flow through Add another account. test/account-merge.test.js covers the ADR's acceptance tests (both sign-ins, resolution, coins once, module fill, moves and clashes, events, the folded token refused, sign-in lands on the survivor, staff reason and owner only, the 30-day reduction, auth_time on renewal). The module and staff-claims tests follow the field fill and staff map 1.2.0.OpenVibers · 27 Sept 2026
899ca7af -
deploy/scripts/deploy.sh hands over to ovhost deploy network --install-units (OpenVibe.Host strategy git-checkout; roadmap WS-N task 11). The old script is kept, unchanged, as deploy/scripts/deploy-legacy.sh. ovhost does what it did (fast-forward pull, npm install --omit=dev when the lockfile changed, deploy/systemd/openvibe-network.service installed when it differs with a daemon-reload, restart) as the checkout owner, checks every dependency before the restart, polls /api/ready, rolls back automatically, records the release and honours freezes; a restart with nothing new now needs --restart. The wrapper passes --restart, --wait-idle, --force, --rollback and DRY_RUN=1 (ovhost plan network); it asks ovhost capabilities network first and runs deploy-legacy.sh, saying why, when ovhost is missing, too old or does not manage network (OVHOST_LEGACY=1 forces it). test/deploy-wrapper.test.js (in npm test). README: a Deploying section.OpenVibers · 27 Sept 2026
d69ec602 -
Web push endpoints cannot be steered inward (roadmap WS-R task 5: SSRF; low). A push subscription's endpoint is a URL the browser hands over, so anyone signed in chose where Network POSTs: web-push sent to it as stored, 127.0.0.1, 169.254.169.254, [::ffff:7f00:1] or a name resolving inward included (a blind POST with Network's VAPID header). The endpoint must now be https, without credentials, on a public host (an IP literal must be public by openvibe-shared/egress's rule, a name not an internal one); subscribe refuses others, rows stored before are dropped at send time, and every push connects through safeLookup, so a DNS answer that is internal, mixed or rebinding never reaches an internal address.OpenVibers · 26 Sept 2026
80caea96 -
Private-bypass and IDOR suites (roadmap WS-R task 5). test/security-private.test.js crawls every GET route Express knows after boot as anonymous, another user, a developer app and a service principal, and no answer may carry alice's private data (email, module data, notification, session device, linked account, history, push endpoint), a project's names to a non-member, or an incident closed 40 days ago; by status, /internal/* never answers 2xx without the key or a capability token, staff pages never answer non-staff, a non-member gets 404 on every read of a project (existence not disclosed) and anonymous 401, personal lists refuse anonymous and app tokens; blocks and follows lists stay their owner's, public module reads carry only public fields; positive controls show each owner does see the data. test/security-idor.test.js swaps ids between two people, two projects and two apps: bob against every write route of alice's project (rename, archive, allowance, environment policy, members, apps, credentials, grants, quotas, export tokens) and alice's ids under his own project's paths, against alice's notifications, session, history, profile, modules, follows, blocks and push subscription, and service principals against another app's notifications, coins, linked accounts, modules and legacy ids. Each refusal leaves the rows as they were and carries no secret; positive controls show each route works for its owner. All held. The shared fixture gives the seeded data distinctive markers and follows channels.OpenVibers · 26 Sept 2026
fd866c04 -
Security crawl for secrets (roadmap WS-R task 5: internal-secret leak). test/security-secrets.test.js boots the real server with a throwaway RS256 and VAPID pair and a fake low-entropy sentinel in every secret it reads from the environment, seeds the database-held ones (OAuth client secrets, developer credentials overlapping after a rotation and their hashes, an export token, a refresh token and its hash, session and anonymous tokens, push keys, provider-secret copies, password hashes, a verification key), then requests every GET route Express knows after boot (test/security-crawl.js walks the router stack from a preload, so a new route is crawled without anyone listing it) as anonymous, a user, a staff admin, the owner, a service principal, a developer app and the legacy key, plus file probes, malformed and empty writes to every write route and wrong credentials. No body or header may carry a secret (also URL-encoded or base64); the log, the event outbox and the audit tables neither. The owner's clear-text reads of provider secrets (owner-guard.js) are the documented exception. test/security-world.js is the shared fixture for the security suites.OpenVibers · 26 Sept 2026
655e8ad5 -
Creator analytics from events (roadmap WS-E task 6; Contracts v0.68.0, pinned from 0.67.0). Network consumes live.stream.ended (added to its topics) into creator_streams: one row per stream, replaced on redelivery, counts only (no viewer, chatter or IP column; pruned after 400 days). GET /api/v1/creators/:creator/analytics?days= gives everyone streams, minutes and peak viewers, daily and per stream. The creator, and services with network.analytics.creator.read (granted to Live for its dashboards), also get average viewers, chatters, messages and watch minutes. test/creator-analytics.test.jsOpenVibers · 26 Sept 2026
45f6c4ce -
Follows, ADR-030 step 4 on Network (Contracts v0.67.0, pinned from 0.66.0). Products record follows on a person's behalf at PUT/DELETE /internal/follows/:type/:target (network.follows.write, service token only, network.follow-write-request@1; granted to Live; the follower must be a Network account; the writing service is recorded as the source). With FOLLOWS_AUTHORITY=network, go-live notifications read the followers from Network's own graph and no longer ask Live (unset to roll back). test/follows-authority.test.jsOpenVibers · 26 Sept 2026
3928b03d -
Incidents and maintenance on /status (roadmap WS-N task 12; Contracts v0.66.0, pinned from 0.65.0). server/status/incidents.js stores incidents (investigating, identified, monitoring, resolved) and maintenance windows (scheduled, in_progress, completed) with their updates. The public GET /api/v1/status/incidents lists active ones and those closed in the last 30 days, and /status shows the open ones at the top. Staff admins, or the Host principal with network.status.incident (granted; ovhost incident / maintenance), open and update them. Closed ones refuse updates, and bodies follow network.status-incident-request@1. test/status-incidents.test.jsOpenVibers · 26 Sept 2026
1a22cd08 -
The follow graph on Network (roadmap WS-E task 4, ADR-030 steps 1-3; Contracts v0.65.0, pinned from 0.63.0). user_follows is keyed by subjects (a Live channel is its owner's subject), with active, notify flags and a per-pair revision. Public count at GET /api/v1/follows/:type/:target (plus the caller's own follow); a person's follows at /api/v1/me/follows with idempotent PUT and DELETE; followers for the target's owner or a service with network.follows.read (granted to Live for its projection). Every change writes network.follow.created or .deleted to the outbox in the same transaction. scripts/follows-backfill.js imports Live's follows (dry run by default; --apply only with a backup; no events; pairs without a subject held in follow_import_holds) and reconciles counts and pairs. test/follows.test.jsOpenVibers · 26 Sept 2026
bb18d98a -
Project usage (roadmap WS-N task 4; Contracts v0.63.0 common.usage-recorded@1, network.project-usage-result@1): the Events consumer (POST /internal/events) now also takes tools.usage.recorded and events.usage.recorded, the owning services' hourly rollups of each developer project's use, and records them inside its inbox transaction (server/developer/usage.js): dev_usage_windows keeps one row per rollup key (service, project, env, capability, dimension, unit, window start; a newer or equal revision replaces it, so a re-sent hour never counts twice; 35 days), dev_usage_daily adds each day up again from its windows (400 days), dev_usage_errors keeps the sampled failures (30 days, 200 per project). Rollups for an unknown project, from the wrong source, with a subject other than their project, a window that is not a whole hour or day, from the future, older than 35 days or failing their contract are ignored, and none notifies anyone. GET /api/v1/projects/:project/usage?days=1-90&env=all|sandbox|production answers the owner, admins and staff (403 developers and viewers, 404 strangers, 422 usage.invalid) with network.project-usage-result@1: daily rows, range totals, each recorded quota (dev_quotas) with used and remaining for its current window (today, this month, the kept total; null with a note for minute and hour windows and for units no service reports) and errors by code with the 50 most recent sampled failures and their trace ids. scripts/subscribe-events.js subscribes the two topics (--topic tools.usage.recorded --topic events.usage.recorded). Pins: openvibe-contracts 0.62.0 -> 0.63.0. test/project-usage.test.js; docs/developer-projects.md#usageOpenVibers · 26 Sept 2026
7e5112a2 -
Operator alerts (roadmap WS-H task 11; Contracts v0.62.0 network.operator.alert): POST /internal/operator/alerts takes Host's service token only (legacy: false) and the complete set of alerts firing on the production host (network.operator-alerts-request@1). server/operator/alerts.js keeps one row per fingerprint and pages the operator (OWNER_USERNAME, plus OPERATOR_ALERT_USERNAMES) with an OPERATOR_ALERT admin notification (bell, web push, realtime; no block hides it) when an alert opens, once a day while it stays open, and when it resolves. Host is granted network.operator.alert. Why: Prometheus evaluated the backup, developer-path and Tools-job alerts, but nothing delivered them. test/operator-alerts.test.jsOpenVibers · 26 Sept 2026
11f2ea5a