openvibers/openvibe.bot Commits

Add the cutover runbook for the signed-in pages and panel PR

OP OpenVibers committed 4 Oct 2026 · bc8a8fd590 · 1 file, +123 −0

Requested by finish-f2. Handed to the local harness by the cloud job pr-OpenVibe.Bot-20; done by Claude sonnet.

Changes

ddiff --git a/docs/cutover-pr-openvibe-bot-20.md b/docs/cutover-pr-openvibe-bot-20.md
nnew file mode 100644
iindex 0000000..e95fe90
---- /dev/null
++++ b/docs/cutover-pr-openvibe-bot-20.md
@@@ -0,0 +1,123 @@
++# OpenVibe.Bot — cutover runbook for PR #20
++
++The harness marks OpenVibe.Bot **risk data** (the repository owns `migrations/`), so a PR that merges into it
++must carry a cutover manifest and a green rehearsal even when the change itself touches no schema. This is the
++runbook for PR #20: what runs in which order, what is backed up, how the result is verified, and the way back.
++
++## Scope — what this PR actually changes
++
++PR #20 (plan T15 step 3, R8) adds the signed-in five-minute path and the profile-rendered panel:
++
++- `server/web/routes.js`, `server/web/render.js` — `GET/POST /robots`, `GET /pair/:id`, `GET /panel/:id`, and
++ the two static assets `/panel/panel.js` and `/panel/panel.css` (from `public/`). Adding a robot and minting a
++ pairing code through these pages count against the signed-in person's `bot.robot.manage` limit, the same one
++ `/api/v1` applies; `/pair/:id` is owner-only and never mints a code for a cross-site navigation or prefetch.
++- `server/sim/index.js`, `server/realtime.js`, `server/app.js`, `server/index.js` — an in-process simulator that
++ drives robots whose profile driver is `sim`. It attaches through the hub (`attachSim`) with no socket and no
++ credential, writes no `devices` row, reports no online/offline event and is not counted in `onlineCount()` or
++ `devices()`. A real device online for the same robot is always preferred. At boot it is started for the `sim`
++ robots that already exist.
++- `deploy/nginx/openvibe.bot.conf` — three `location` blocks (`= /robots`, `^~ /pair/`, `^~ /panel/`) proxied to
++ port 4630 under the existing `ovbot_api` rate-limit zone.
++- `public/panel.js`, `public/panel.css`, `test/panel.test.js`, `docs/protocol.md`, `README.md`, `STATUS.json`.
++
++**No migration and no schema change.** `migrations/` is untouched (`0001`–`0004` are already applied on the base
++branch). The only data the new pages write is what the existing domain calls write: a `robots` row (and its
++owner membership and first pairing code) when a person adds a robot, and a fresh `pairing_codes` row when the
++owner opens `/pair/:id` — the same rows `POST /api/v1/robots` and the pairing API write today. Deploying the PR
++itself writes nothing; the first rows appear only when a signed-in person uses the new pages.
++
++## Cutover manifest (the harness writes it into the PR description)
++
++The merge gate reads `runbook` and `rehearsal` from a fenced `cutover` block in the PR description. The harness
++adds it from this file at the verified head; it resolves to:
++
++```cutover
++{"runbook": "docs/cutover-pr-openvibe-bot-20.md", "rehearsal": "openvibe-bot-20"}
++```
++
++- `runbook` — this file, the runbook the rehearsal follows.
++- `rehearsal` — the marker name `openvibe-bot-20`; the rehearsal writes
++ `ds/deploy/rehearsals/openvibe-bot-20.json` = `{"ok": true}` on the harness when it is green. That marker is
++ written by whoever runs the rehearsal, never by this runbook and never by the PR author.
++
++## What runs, in which order
++
++1. **Merge gate (harness).** Checks green, review SHIP, this runbook on the verified head, and
++ `ds/deploy/rehearsals/openvibe-bot-20.json` saying `{"ok": true}` for the pushed head. Only then does the
++ squash-merge run.
++2. **Backup.** Take the standard pre-deploy backup of Bot's PostgreSQL database (pgBackRest) and confirm the
++ latest restore point is current. This PR needs no restore for its own sake — it changes no schema — but the
++ guard is unconditional: no deploy starts on an unbacked database.
++3. **Deploy.** Bot deploys through its own pipeline (`ov deploy bot` queues the guarded deploy on
++ `openvibe-ovh`; unit `openvibe-bot.service`, layout `/opt/openvibe.bot`, port 4630). The release ships the
++ nginx vhost `deploy/nginx/openvibe.bot.conf`; validate it (`nginx -t`) and reload nginx **after** the new
++ release is serving, so the three new locations never proxy to a process that does not know them. On boot the
++ service applies `migrations/` with the owner role on `DATABASE_DIRECT_URL` (0 pending), serves on the pooled
++ `DATABASE_URL`, and starts the simulator for existing `sim` robots.
++4. **Verify.** Run the checks in the next section before the deploy is called good. A failed readiness check
++ rolls the release back automatically.
++
++## The backup
++
++- Command: the standard pgBackRest backup of the Bot database (the same one every Bot deploy takes), then
++ `pgbackrest info` to confirm a restore point newer than the deploy start.
++- No import step applies: Bot is already on PostgreSQL (schema in `migrations/0001_bot.sql` through
++ `0004_run_jobs.sql`, all already applied on the base branch).
++- Row counts of `robots`, `devices`, `robot_operators`, `pairing_codes`, `command_audit` and `bot_event_outbox`
++ are noted before the deploy for the verification step.
++
++## How the result is verified
++
++1. `openvibe-bot.service` is active after the restart (no crash loop) and the journal shows no
++ `[Bot] simulator:` warning; with no `sim` robots yet it prints nothing for the simulator.
++2. `GET http://127.0.0.1:4630/api/health` answers 2xx; its online-device count is unchanged (a simulator is
++ never counted as a machine).
++3. `GET http://127.0.0.1:4630/api/ready` answers 2xx: database, Valkey, Network readiness and the outbox relay.
++4. Signed out, `GET https://openvibe.bot/robots`, `/pair/<id>` and `/panel/<id>` each answer HTTP 302 to
++ `/auth/login?next=<the page>` with `Cache-Control: no-store`; `/panel/panel.js` and `/panel/panel.css` answer
++ 200 with the right content types. `GET /install` is still a 302 to `BOT_INSTALLER_SOURCE_URL`.
++5. Signed in as a test person: `/robots` lists only that person's robots; adding a `sim` robot answers 303 to
++ its panel, which renders from the profile and shows the profile's allowed commands; a second person gets 403
++ on that panel and on `/pair/<id>` (owner only). Delete or leave the test robot as the owner decides.
++6. The migrations table lists the same applied migrations as before the deploy — **0 pending** — and the
++ row counts from the backup step differ only by the test robot added in step 5.
++
++If any of 1–4 fails, the deploy is not good: roll back (below) and investigate.
++
++## Way back (rollback and restore)
++
++- **Automatic:** while the deploy waits for readiness, a non-2xx `/api/ready` restores the previous release's
++ sha. The previous release ignores everything this PR added, so it serves immediately.
++- **Manual:** `ovhost rollback bot --to <previous sha>` on `openvibe-ovh`, then restore the previous nginx vhost
++ (the three new `location` blocks are additive; without them `/robots`, `/pair/` and `/panel/` simply 404 at
++ the edge), `nginx -t` and reload, then re-run the verification above.
++- **Configuration:** no new configuration key; there is nothing to unset.
++- **Data:** no migration ran, so there is no schema to roll back. Rows written through the new pages (robots,
++ memberships, pairing codes) are ordinary rows the previous release reads like any other and may stay; a
++ `sim` robot just has no device behind it after a rollback. Restore from the pgBackRest backup taken in the
++ backup step **only** if an unrelated data incident requires it, coordinating with the database owner because
++ a restore discards writes made after the snapshot.
++
++## Rehearsal on a copy of the data
++
++Whoever runs this runbook rehearses it **on a copy** — never on production — against a scratch PostgreSQL, with
++main's migrations, the repository's fixtures and this PR's head. The fenced block declares the commands the
++harness runs after it has applied the migrations:
++
++```rehearse
++# PR 20 adds no migration; this proves the signed-in pages, the profile-rendered panel, the simulator and the
++# per-person write limits, and that the unchanged installer redirect still holds.
++node test/panel.test.js
++node test/install.test.js
++```
++
++1. The harness starts a scratch database, applies the base branch's migrations and the repository's fixtures,
++ then this PR's migrations (none new: a second run must apply nothing).
++2. It runs `node test/panel.test.js` (the pages' sign-in redirects, owner/member/queue access, the same-origin
++ and prefetch fences, the panel rendered from the profile, and the simulated robot) and
++ `node test/install.test.js` (the `/install` redirect and the per-profile `--driver` suffix).
++3. It confirms 0 pending migrations against the copy.
++4. When all of it is green, record the marker `ds/deploy/rehearsals/openvibe-bot-20.json` = `{"ok": true}` on
++ the harness. **This runbook must not write that marker itself** — it is written by whoever performed the
++ rehearsal, from the runbook's exit code.