openvibers/openvibe.bot Commits

Add the cutover runbook for the embed framing contract docs

OP OpenVibers committed 4 Oct 2026 · aab2746f27 · 1 file, +110 −0

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

Changes

ddiff --git a/docs/cutover-pr-openvibe-bot-25.md b/docs/cutover-pr-openvibe-bot-25.md
nnew file mode 100644
iindex 0000000..229a6fe
---- /dev/null
++++ b/docs/cutover-pr-openvibe-bot-25.md
@@@ -0,0 +1,110 @@
++# OpenVibe.Bot — cutover runbook for PR #25
++
++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. PR #25 adds **no migration** and changes **no code**: it
++documents the two-sided framing contract of the embed panel. So this is a no-schema, no-code cutover — 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 #25 states that framing the embed panel takes both sides, and that the two allow-lists are independent:
++`BOT_EMBED_ORIGINS` lets a page frame Bot (CSP `frame-ancestors` on `/panel/:id/embed`), while that page must
++list Bot's origin (its `BASE_URL`, `https://openvibe.bot` in production) in its own CSP `frame-src`
++(OpenVibe.Live computes it from `LIVE_BOT_URL`, default `https://openvibe.bot`).
++
++- `docs/protocol.md` (§2.1) — the paragraph on the embed response's CSP.
++- `README.md` — the same contract next to the `BOT_EMBED_ORIGINS` and `/watch` paragraph.
++- `.env.example` — two comment lines on `BOT_EMBED_ORIGINS`; the commented-out value is unchanged.
++- `docs/cutover-pr-openvibe-bot-25.md` — this runbook.
++
++There is **no migration and no data-shaped change**: no setting, route, header or row is added or altered, the
++runtime reads the same variables with the same defaults, and `.env.example` is a template that no deploy reads.
++The previous release serves the same schema and rows identically; a rollback is a plain app rollback with no
++data step.
++
++## 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-25.md", "rehearsal": "openvibe-bot-25"}
++```
++
++- `runbook` — this file, the runbook the rehearsal follows.
++- `rehearsal` — the marker name `openvibe-bot-25`; the rehearsal writes
++ `ds/deploy/rehearsals/openvibe-bot-25.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-25.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 change touches no schema and writes no rows, 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 service restarts on
++ unchanged code; `migrations/` has **0 pending** (a second run applies nothing). No nginx or
++ `/etc/openvibe/bot.env` change is needed.
++4. **Verify.** Run the checks in the next section before the deploy is called good.
++
++## 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 (`migrations/0001_bot.sql` through
++ `0005_embed_public.sql`, all 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; they must be unchanged afterwards apart from live traffic.
++
++## How the result is verified
++
++1. `openvibe-bot.service` is active after the restart (no crash loop) and the journal shows **0 pending**
++ migrations.
++2. `GET http://127.0.0.1:4630/api/health` and `GET http://127.0.0.1:4630/api/ready` answer 2xx.
++3. Behaviour is unchanged: anonymous `GET /panel/<id>/embed` for a robot with embedding **on** still answers
++ the watcher panel with `Cache-Control: no-store` and a `Content-Security-Policy` whose `frame-ancestors` is
++ `'self'` plus exactly the `BOT_EMBED_ORIGINS` list; with embedding **off** it still answers `403`; every
++ other page still sends `frame-ancestors 'self'`. Anonymous `wss://openvibe.bot/watch` still answers
++ `joined` with `role: "watcher"` for a public robot.
++4. The documentation says what the behaviour is: `docs/protocol.md` §2.1 and the README embed paragraph both
++ say the embedding page must list Bot's origin in its own `frame-src`, and `.env.example` carries the same
++ note above `BOT_EMBED_ORIGINS`.
++5. The row counts from the backup step are unchanged.
++
++If 1–3 fail, the deploy is not good: roll back (below) and investigate.
++
++## Way back (rollback and restore)
++
++- **Automatic:** a non-2xx `/api/ready` while the deploy waits for readiness restores the previous release's
++ sha; it runs identically on the same schema and rows.
++- **Manual:** `ovhost rollback bot --to <previous sha>` on `openvibe-ovh`, then re-run the verification above.
++- **Configuration:** nothing changed; no setting needs to be restored.
++- **Data:** there is **no data step to undo**. This PR adds no migration and writes no row. 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 25 adds no migration and no code. This proves the docs and README tests still hold and that the embed
++# panel and its CSP behave as the new text describes, against the migrated scratch schema.
++node test/docs.test.js
++node test/embed-panel.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**: a second run applies nothing.
++2. It runs `node test/docs.test.js` (README and STATUS.json agree with package.json) and
++ `node test/embed-panel.test.js` (the embed route's own CSP with `frame-ancestors` from
++ `BOT_EMBED_ORIGINS`, `no-store`, the watcher socket and its caps).
++3. It confirms **0 pending** migrations afterwards and the fixture row counts unchanged.
++4. When all of it is green, record the marker `ds/deploy/rehearsals/openvibe-bot-25.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.