openvibers/openvibe.space README.md
README.md
| 1 | # OpenVibe.Space |
| 2 | |
| 3 | **https://openvibe.space — spaces (apps people make on their OpenVibe account) and static hosting for the OpenVibe network.** |
| 4 | |
| 5 | **Live site:** https://openvibe.space · **API docs:** https://openvibe.services · **For AI:** https://openvibe.space/llms.txt |
| 6 | **Run it:** `npm ci && npm run dev` (Node 22 or newer) · **Tests:** `npm test` |
| 7 | |
| 8 | ## Purpose and status |
| 9 | |
| 10 | Space owns static hosting. The Stage B engine moved here from OpenVibe.Host as code; production had no projects, sites or deploys to migrate. It supports projects and members, immutable content-addressed deploys, activation and one-step rollback, private previews, CI Git ingest, per-site configuration, verified custom domains, takedowns, Media-backed objects, Host-header tenant serving, and a resource index. Tenant names serve at `<name>.openvibe.website`. |
| 11 | |
| 12 | Public hosting remains off by default (`SPACE_PUBLIC_HOSTING=off`). Only OpenVibe staff can create projects. Abuse handling and a designated DMCA agent precede public launch. |
| 13 | |
| 14 | **Spaces** (plan T20 step 2; `server/spaces/`, `docs/spaces.md`) are small web apps people make here: described to the AI |
| 15 | builder (OpenVibe.AI `space.app.build`), started from a template (guestbook, AI buddy, poll, reaction game, trivia, |
| 16 | private to-dos, blank) or forked, then edited in a browser editor with a live preview. A space uses the visitor's |
| 17 | OpenVibe account through the space SDK (`/_openvibe/sdk.js`): sign-in that the visitor approves once, shared lists |
| 18 | (`OV.items`), private storage (`OV.kv`), the space's own AI (`OV.ai.ask`, OpenVibe.AI `space.app.ask` on behalf of |
| 19 | the visitor) and its own server code (`functions/<name>.js`, run in a QuickJS WebAssembly sandbox at `/api/<name>` |
| 20 | and from `OV.fn`). Making, editing, previewing and the AI builder work for everyone signed in. Publishing deploys a space to |
| 21 | a site of its maker's Spaces project at `<slug>.openvibe.website`, so it follows the same launch switch: staff until |
| 22 | `SPACE_PUBLIC_HOSTING=on`. |
| 23 | |
| 24 | OpenVibe.Host is the hosting price comparison; its copy of the engine is being removed, and its old hosting URLs will answer 410 with a link here. |
| 25 | |
| 26 | ## Owns |
| 27 | |
| 28 | - Spaces: their source files and settings, the gallery, the editor, the space SDK and its API, each space's shared lists, |
| 29 | its visitors' private storage, the sign-ins visitors approved, and the daily AI allowances of spaces and builders. |
| 30 | - Static hosting: projects and members, sites, immutable content-addressed deploys, activation and rollback, |
| 31 | previews, Git ingest, per-site configuration, custom domains with TXT verification, and takedowns. |
| 32 | - Serving tenant sites by Host header at `<name>.openvibe.website` and on verified custom domains. |
| 33 | - Its tables in `ov_space` (`space_*`), its control log and its outbox (`space.deploy.*`, `space.domain.verified`). |
| 34 | |
| 35 | ## Does not own |
| 36 | |
| 37 | - People, sessions and projects' identity (OpenVibe.Network), stored bytes when `SPACE_OBJECT_STORE=media` |
| 38 | (OpenVibe.Media), money and usage rating (OpenVibe.Billing), the forum (OpenVibe.Community, see below) and the |
| 39 | hosting price comparison (OpenVibe.Host). |
| 40 | |
| 41 | ## Depends on |
| 42 | |
| 43 | - **OpenVibe.Network:** sign-in (OAuth with PKCE), tokens and their keys, project membership. |
| 44 | - **OpenVibe.Events:** the outbox relay, when `EVENTS_URL` is set, and account export and deletion (ADR-033): |
| 45 | `network.account.export_requested` and `network.account.deleted` reach the loopback `/internal/events` consumer |
| 46 | (`server/account-data.js`, openvibe-sdk/account-data), signed with `SPACE_EVENTS_SECRET`. A person's spaces are |
| 47 | exported with their files; on deletion their published spaces stop serving and the spaces go with what others kept |
| 48 | in them, and their list items, private storage and sign-ins leave other people's spaces. |
| 49 | - **OpenVibe.Media:** tenant objects, when `SPACE_OBJECT_STORE=media`. |
| 50 | - **OpenVibe.Billing:** storage readings, when `SPACE_BILLING_INTERVAL_MS` is on. |
| 51 | - **OpenVibe.Services:** reads the resource index and sends control requests. |
| 52 | - **OpenVibe.AI:** the space builder and a space's own AI (`space.*` workflows; Network grants `ai.run.create` and |
| 53 | `ai.run.read` on `space.*`). |
| 54 | |
| 55 | ## The forum moved to OpenVibe.Community |
| 56 | |
| 57 | The forum lived here from 3 October 2026 (it moved off Community then). On **2026-10-08 the owner |
| 58 | moved it back**: OpenVibe.Community owns the forum (spaces, threads, posts, votes, categories, |
| 59 | per-space moderators, the Discord relay) again, and Space became the network's hosting (static sites now; dynamic pages and spaces next). |
| 60 | |
| 61 | So Space keeps no forum code and **every forum URL answers a permanent redirect to the forum's |
| 62 | address on https://openvibe.community** (which moved from `/s` to `/forum` on 2026-11-07): |
| 63 | |
| 64 | | Path | Methods | Status | Goes to | |
| 65 | | --- | --- | --- | --- | |
| 66 | | `/s`, `/s/*` (pages, feeds) | GET, HEAD | `301` | `/forum/<space>/<thread>` (the `/t/` segment is gone) | |
| 67 | | `/s`, `/s/*` | anything else | `308` (the method and body are preserved) | the same `/forum` path | |
| 68 | | `/api/v1/spaces*`, `/api/v1/posts/*`, `/api/v1/space-groups*`, `/api/v1/relay*` | all | `308` | the same API path | |
| 69 | |
| 70 | A redirected bookmark, crawler or API client therefore lands on Community's copy in one hop. |
| 71 | Nothing that is not a forum path redirects. |
| 72 | |
| 73 | ## Routes |
| 74 | |
| 75 | | Route | Purpose | |
| 76 | | --- | --- | |
| 77 | | `/` | Spaces and static hosting | |
| 78 | | `/spaces`, `/spaces/new`, `/spaces/mine`, `/spaces/:slug` | The gallery and templates, making a space (template or AI builder), your spaces, a space's page | |
| 79 | | `/spaces/:slug/edit` and its JSON calls (`files`, `settings`, `ai`, `publish`, `unpublish`, `delete`, `preview`) | The editor (maker or staff; same origin with the form token in `X-CSRF-Token`) | |
| 80 | | `/spaces/:slug/fork`, `/spaces/:slug/connect` | Fork a space; the sign-in a visitor approves for a published space | |
| 81 | | `/_openvibe/sdk.js` | The space SDK: live on a space's own address, preview mode on openvibe.space | |
| 82 | | `/api/v1/apps/:id/token`, `/api/v1/apps/:id/run`, `/api/v1/apps/:id/items/:collection` | The space SDK's API, from the space's own origin only (CORS for that origin, Bearer space token, never cookies) | |
| 83 | | `/dashboard`, `/projects/:id`, `/sites/:id`, `/deploys/:id` | Signed-in hosting dashboard and forms | |
| 84 | | `/preview/:deployId/*` | Private, sandboxed deploy preview | |
| 85 | | `/api/v1/projects`, `/api/v1/sites`, `/api/v1/deploys`, `/api/v1/domains` | Hosting API (Bearer tokens, no cookie auth) | |
| 86 | | `/api/v1/resources` | First-party resource index | |
| 87 | | `POST /api/v1/resources/control` | First-party control: `delete` a site, deploy or domain for a person (Space's own roles decide) | |
| 88 | | `/limits.json` | Enforced hosting limits | |
| 89 | | `/auth/login|callback|fedcm|logout|me|refresh` | Space's Network OAuth client | |
| 90 | | `/robots.txt`, `/llms.txt`, `/llms-full.txt`, `/sitemap.xml` | Discovery for Space's public page | |
| 91 | | `/api/health`, `/api/ready`, `/release.json`, `/metrics` | Health, release and observability | |
| 92 | |
| 93 | Sitemap entries carry lastmod from the release commit or, for hosted pages, the active deploy. |
| 94 | |
| 95 | Tenant requests are dispatched by Host header before dashboard routes. `deploy/nginx/openvibe.website.conf` is a reference wildcard vhost; the existing `openvibe.space` vhost includes larger upload locations. Neither is installed by this code change. Unknown hosts answer 404. Tenant sites cannot reach the dashboard, API, auth or metrics. A custom domain serves only after TXT verification. |
| 96 | |
| 97 | ## Capabilities and events |
| 98 | |
| 99 | The hosting API guards `space.site.manage`, `space.site.config`, `space.deploy.create`, `space.domain.manage`, `space.resource.read` and `space.resource.control` (openvibe-contracts 0.170.0, owner `space`); every route names exactly one, and `npx openvibe-contracts-check --service space` checks the code against the release. Service tokens need audience `openvibe.space`, and project membership still applies. The per-actor limits (`server/http/actor-limits.js`) count uploads, domain checks, project creation and writes per person or app; those are limit buckets, not capabilities. The retired `space.forum.*` contracts are unrelated to hosting. The control route (`server/registry/resource-control.js`, plan T13 step 10) takes a `common.resource-control-request@1` from OpenVibe.Services and acts as the person in `on_behalf_of` through the same domain code as the DELETE routes: deleting needs maintainer or owner, a non-member gets `resources.unknown_resource`, and the served deploy (`deploy.active`), a default domain (`domain.default`) and anything taken down are refused with Space's own reason. Dry runs; repeats with the same key return the first answer (`space_control_log`). |
| 100 | |
| 101 | The transactional outbox emits `space.deploy.created`, `space.deploy.activated`, `space.deploy.failed` and `space.domain.verified`; each has a payload schema in openvibe-contracts, and the SDK outbox refuses any other type. |
| 102 | |
| 103 | ## Data and configuration |
| 104 | |
| 105 | `migrations/0002_initial.sql` through `0005_git_sources.sql` create the hosting tables, `0006_billing_readings.sql` the usage-reading outbox,, indexes and immutable-artifact triggers from Host Stage B with `space_` names. They run through `openvibe-sdk/db` at boot; tables are never created by a request. `0001_initial.sql` remains the historical forum schema, which this release does not drop. This is a code move, with no data copy. |
| 106 | |
| 107 | `migrations/0009_spaces.sql` creates the spaces tables (`space_apps`, `space_app_files`, `space_app_projects`, `space_app_items`, `space_app_kv`, `space_app_grants`, `space_app_codes`, `space_app_tokens`, `space_app_counters`). Only hashes of sign-in codes and tokens are kept. |
| 108 | `migrations/0010_account_data.sql` creates openvibe-sdk/account-data's `account_data_events` (the forum-era table of that name is kept as `account_data_events_forum`). |
| 109 | |
| 110 | Request telemetry is aggregated by route template, method and status class, never by raw URL. Once a minute one `http.request` sample per key, plus the active-requests, p95 latency and event-loop-lag gauges, is written to this service's own `telemetry_samples` table (openvibe-sdk 0.46 telemetry, the `platform.telemetry-sample@1` shape), which `migrations/0008_telemetry.sql` creates; rows older than 14 days are deleted daily. `/shared` files, static assets, health, readiness and `/metrics` are not counted. `server/app.js` mounts openvibe-sdk's `telemetryMiddleware` (a no-op until init); `server/index.js` calls `telemetry.init()` at production start, runs the 14-day retention and flushes on stop. |
| 111 | |
| 112 | Copy `.env.example` to `.env`. Production uses `/etc/openvibe/space.env` (0600). Important values: |
| 113 | |
| 114 | | Variable | Default | Purpose | |
| 115 | | --- | --- | --- | |
| 116 | | `SPACE_PUBLIC_HOSTING` | `off` | Staff-only project creation (and publishing spaces) until launch | |
| 117 | | `SPACE_EVENTS_SECRET` | unset | Signs OpenVibe.Events deliveries to `/internal/events` (account export and deletion); unset answers 503 | |
| 118 | | `SPACE_AI`, `OV_AI_INTERNAL_URL` | `on`, `http://127.0.0.1:4700` | The space builder and spaces' AI through OpenVibe.AI | |
| 119 | | `SPACE_SPACES_PER_OWNER`, `SPACE_AI_BUILDS_PER_DAY`, `SPACE_AI_ASKS_PER_SPACE_PER_DAY` | 25, 20, 500 | Spaces per person, AI builds per person a day, AI answers per space a day (more `SPACE_SPACE_*` limits in `server/hosting-config.js`) | |
| 120 | | `SPACE_SITES_DOMAIN` | `openvibe.website` | Tenant names | |
| 121 | | `SPACE_STORAGE_DIR` | `./data/objects` | Content-addressed local object store | |
| 122 | | `SPACE_OBJECT_STORE` | `local` | Set `media` for Media write-through and cache | |
| 123 | | `SPACE_FORM_SECRET` | random per process | Dashboard CSRF form signing | |
| 124 | | `SPACE_WORKER` | on | Domain rechecks, object sweep, outbox pruning | |
| 125 | | `EVENTS_URL` | unset | Outbox relay destination; unset keeps rows queued | |
| 126 | | `SPACE_BILLING_INTERVAL_MS` | `0` (off) | Daily `gb-month` storage readings to OpenVibe.Billing (`OV_BILLING_URL`) in local mode, per OpenVibe.Network project (`network_project_id`; a project that names none gets no reading); Media mode emits none (Media bills the bytes) | |
| 127 | | `DATABASE_URL`, `DATABASE_DIRECT_URL` | unset | PostgreSQL serving and owner migration connections | |
| 128 | |
| 129 | Other `SPACE_*` quota, upload, domain and DNS settings are listed in `.env.example` and `server/hosting-config.js`. Space keeps its existing OAuth and forum redirect settings. |
| 130 | |
| 131 | ## Tests and running |
| 132 | |
| 133 | ```sh |
| 134 | npm install |
| 135 | npm start |
| 136 | npm test |
| 137 | ``` |
| 138 | |
| 139 | `test/hosting-core.test.js` exercises migrations, launch gate, project/site creation, tenant dispatch, immutable deploys, pointer rollback and events directly against PGlite without a socket. The ported Stage B integration files under `test/hosting-*.test.js` use `test/stageb/boot.js` and raw HTTP for Host-header and request-target cases. The existing `home`, `redirects`, and `boot` tests cover Space's public app and forum redirects. |
| 140 | |
| 141 | ## Security and dependencies |
| 142 | |
| 143 | Static uploads are validated before storage; tenant content runs on a separate registrable domain and previews use a sandbox CSP. APIs ignore cookies. OpenVibe.Network supplies identity; `svc:space` needs `events.event.publish`, `billing.usage.record` (audience `openvibe.billing`) once `SPACE_BILLING_INTERVAL_MS` is on, and, when `SPACE_OBJECT_STORE=media`, the `media.object.upload/read/list/delete` grants for the `space` namespace. OpenVibe.Media may hold tenant objects; OpenVibe.Events receives the outbox when configured. Space's own database is `ov_space` through `openvibe-sdk/db`. See [SECURITY.md](SECURITY.md). |
| 144 | |
| 145 | ## Deploy |
| 146 | |
| 147 | `sudo ovhost deploy space` on the host: the release layout under `/opt/openvibe.space`, `/etc/openvibe/space.env`, the |
| 148 | `openvibe-space` unit, migrations at boot. The vhosts are `deploy/nginx/` (installed with `ov-vhost-install`). |
| 149 | |
| 150 | ## License |
| 151 | |
| 152 | AGPL-3.0-only. See [LICENSE](LICENSE). |
| 153 | |
| 154 | <!-- versions:start --> |
| 155 | - openvibe-contracts: v0.419.0 |
| 156 | - openvibe-sdk: v0.161.0 |
| 157 | - openvibe-shared: v4.241.0 |
| 158 | <!-- versions:end --> |
| 159 |