openvibers/openvibe.tools Commits

Docs: close out the T8 PostgreSQL cutover (README + evidence + guard)

OP OpenVibers committed 4 Oct 2026 · 5a1f33e056 · 3 files, +92 −2

Asked: T0 step 1 — OpenVibe.Tools: close out the T8 PostgreSQL cutover in the docs. ONE pull request from current origin/main (git fetch first). Docs and one guard test only; no code, migration or version change. The attached T0 dossier, section "T0 step 1", has the exact lines and the evidence skeleton. …
Harness job tools-t0-step-1-openvibe-tools-122728.

Changes

ddiff --git a/README.md b/README.md
iindex 7ab9b30..d1d8c90 100644
---- a/README.md
++++ b/README.md
@@@ -643,11 +643,12 @@ After the move the apps read `DATABASE_URL` / `DATABASE_DIRECT_URL` (PostgreSQL,
never authoritative), while `guard_abuse`, the job store and the analytics tables stay in PostgreSQL.
`TOOLS_GUARD` (default `report`) and its meaning are unchanged.
--State (plan T8 phases 1–4 and 6 done, not deployed; the cutover is pending): every app opens the one `tools`
++State (plan T8 landed 2026-10-02; production serves the PostgreSQL-only release): every app opens the one `tools`
database (`apps/_shared/db.js`, `DATABASE_URL` else an embedded PGlite database in development;
`DATABASE_DIRECT_URL` runs the migrations as owner) and the shared Valkey (`VALKEY_URL`, else the guard's
counters and salt stay in this process); the job store, `guard_abuse`, `token_revocations` and the request
analytics are on PostgreSQL, the net and dev pages call the run API, and the `/api/process`, `/api/net/*` and
`/api/dev/*` routes are removed (above). No app depends on `better-sqlite3` any more; the operational scripts
that still read the old SQLite files (the importer, `scripts/analytics-prune.js`, `scripts/guard-abuse-report.js`)
--have it in `scripts/package.json`: run `npm --prefix scripts install` once on the host before the cutover.
++have it in `scripts/package.json`: run `npm --prefix scripts install` on the host only to read the archived SQLite
++files (the importer has run; production writes PostgreSQL). The cutover evidence is in `docs/cutover-evidence-t8.md`.
ddiff --git a/apps/_shared/test/cutover-evidence.test.js b/apps/_shared/test/cutover-evidence.test.js
nnew file mode 100644
iindex 0000000..48659c4
---- /dev/null
++++ b/apps/_shared/test/cutover-evidence.test.js
@@@ -0,0 +1,22 @@
++'use strict';
++// The T8 closeout record must exist and keep its checks, and the README must no longer say the cutover is
++// pending (plan T8; a silent re-draft would lose the evidence). This fails loudly if either regresses.
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++
++const ROOT = path.join(__dirname, '..', '..', '..');
++const doc = path.join(ROOT, 'docs', 'cutover-evidence-t8.md');
++const readme = path.join(ROOT, 'README.md');
++
++assert.ok(fs.existsSync(doc), 'docs/cutover-evidence-t8.md is missing');
++const md = fs.readFileSync(doc, 'utf8');
++for (const key of ['health tools', 'origin/main HEAD', 'env', 'Valkey ACL', 'Row parity', 'Reproduce']) {
++ assert.ok(md.includes(key), `docs/cutover-evidence-t8.md is missing "${key}"`);
++}
++
++assert.ok(fs.existsSync(readme), 'README.md is missing');
++const readmeText = fs.readFileSync(readme, 'utf8');
++assert.ok(!readmeText.includes('the cutover is pending'), 'README.md still contains "the cutover is pending"');
++
++console.log('cutover evidence exists, records its checks, and the README no longer says the cutover is pending: passed');
ddiff --git a/docs/cutover-evidence-t8.md b/docs/cutover-evidence-t8.md
nnew file mode 100644
iindex 0000000..6df9e21
---- /dev/null
++++ b/docs/cutover-evidence-t8.md
@@@ -0,0 +1,67 @@
++# T8 cutover: closeout evidence — OpenVibe.Tools
++
++What the read-only interfaces can actually prove about the Tools PostgreSQL + Valkey cutover (plan T8). The
++cutover landed **2026-10-02**; production serves the PostgreSQL-only release. Checked **2026-10-04 (UTC)** through
++`ov access run openvibe-ovh <action>` — `health`, `service-status`, `plan`, `releases`, `validate`, `env-set`,
++`show`, `journal`. Every result below is a literal output of one of those actions.
++
++Nothing here was deployed, restarted, rolled back or imported; no credential, URL or env **value** was read or
++printed. Checks that need an interface the broker does not expose are marked **pending** in their own section and
++were not guessed.
++
++SHAs are printed as `ovhost` reports them (12 hex characters).
++
++## Release and readiness — verified
++
++- `health tools` (exit 0): `tools a331ea36963a ready openvibe-tools=active openvibe-tools-maps=active
++ openvibe-tools-food=active openvibe-tools-img=active openvibe-tools-yt=active openvibe-tools-audio=active
++ openvibe-tools-text=active openvibe-tools-docs=active running tool jobs=0`. `ovhost` needs each service's
++ `/api/ready` to answer before it calls the service `ready`.
++- `service-status tools` (exit 0): the same line.
++- `plan tools` (exit 0): `tools: a331ea36963a → a331ea36963a (up to date) on main`; strategy `multi-app`, `0`
++ files changed, `restart no`.
++- `validate tools` (exit 0): `ok checkout /opt/openvibe.tools owned by ubuntu`; `ok checkout clean at
++ a331ea36963a`; all eight `openvibe-tools*` units `active/running`; `ok port 4001 held by node`; `ok deps every
++ dependency resolves`; `tools: valid (0 error(s), 0 warning(s))`.
++- `releases tools` (exit 0): `834bed39e5b8 → 98f8bebf73ee → a331ea36963a deployed` (2026-10-03T11:35Z,
++ 2026-10-04T00:24Z, 2026-10-04T03:00Z). `a331ea36963a` is origin/main HEAD and the release running in
++ production; `834bed39e5b8` was the PostgreSQL-only release deployed during the cutover.
++
++**Why this is a PostgreSQL-only release.** Every app boots through `apps/_shared/db.js`, which in production
++throws `DATABASE_URL is not set: Tools serves from PostgreSQL (plan T8)` and only falls back to embedded PGlite
++(PGlite outside production). A release that is `active` and `ready` therefore booted with `DATABASE_URL` set and
++serves from PostgreSQL. No app depends on `better-sqlite3` any more, guarded by
++`apps/_shared/test/no-sqlite.test.js`; the migrations (`migrations/0001_tools.sql` … `0004_event_outbox.sql`) run
++only as the owner via `DATABASE_DIRECT_URL`, never at runtime.
++
++## Env presence and mode — verified
++
++- `validate tools`: `ok env /etc/openvibe/tools.env mode 600`; `ok env 4 required name(s) present and non-empty`.
++- `env-set tools` (names only, `empty: []`): **`DATABASE_URL`**, **`DATABASE_DIRECT_URL`**, **`VALKEY_URL`**,
++ `VALKEY_PREFIX`. `fromUnits`: `NODE_ENV`, `PATH`, `PORT`.
++
++## Valkey ACL scope — pending
++
++Presence of `VALKEY_URL` and `VALKEY_PREFIX` is provable (above). The scope of the Valkey user — which commands
++it holds and that it is confined to `~ov:tools:*` (`/etc/valkey/services.acl`) — needs a connection to Valkey
++that no read-only action opens. **Not checked.**
++
++## Row parity / rollback copy — pending
++
++The importer has already run and production writes PostgreSQL, but the per-table parity report and the rollback
++copy (`$COPY` = `/var/backups/openvibe/tools-pre-t8-<T0>.db`, `chmod 0400`) are not exposed by any read-only
++action, which runs no query and lists no file. **Not checked.**
++
++## Reproduce
++
++```bash
++ov access run openvibe-ovh health tools
++ov access run openvibe-ovh service-status tools
++ov access run openvibe-ovh plan tools
++ov access run openvibe-ovh releases tools
++ov access run openvibe-ovh validate tools
++ov access run openvibe-ovh env-set tools
++ov access run openvibe-ovh journal openvibe-tools.service 200
++```
++
++Every one of these is a low-risk read. `deploy`, `rollback`, `restart` and `db-backup` were not used.