openvibers/openvibe.ai Commits
The SQLite era is gone (on PostgreSQL since 2026-09-28; no compatibility kept): the one-time migrate-to-postgres script, *_DB_PATH settings (config, .env.example, systemd unit, test helpers), the unused better-sqlite3 dependency and its lockfile entries, and docs, docstrings and STATUS lines about SQLite or its rollback window. Reviewed independently; its blockers (an edited applied migration, the lockfile) are resolved here, Host's inventory follow-up is separate. npm test 23/23 test files passed.
Changes
@@@ -10,16 +10,13 @@ HOST=127.0.0.1NODE_ENV=developmentBASE_URL=http://localhost:4700--# SQLite (production unit: /var/lib/openvibe-ai/ai.db via StateDirectory=openvibe-ai)# PostgreSQL (ADR-035), written by OpenVibe.Host roles/data/add-service.sh ai: the runtime role through PgBouncer,--# the owner on a direct connection (migrations and the one-time import), and Valkey (shared limit counters).++# the owner on a direct connection (migrations), and Valkey (shared limit counters).# Without DATABASE_URL, development uses an embedded PGlite database in data/pglite.DATABASE_URL=DATABASE_DIRECT_URL=VALKEY_URL=VALKEY_PREFIX=--# The SQLite file of releases before PostgreSQL: read once by scripts/migrate-to-postgres.js.--AI_DB_PATH=./data/ai.db# Identity: OpenVibe.Network signs the service tokens callers present (audience openvibe.ai).# The public key is fetched from <Network>/api/.well-known/jwks; OV_NETWORK_PUBLIC_KEY pins a PEM instead.@@@ -52,8 +49,7 @@ OV_NETWORK_INTERNAL_URL=http://127.0.0.1:4000# ── The shared provider — the same names as OpenVibe.Live's admin AI settings ──# (ai_enabled, ai_provider, ai_base_url, ai_api_key, ai_model, ai_model_<role>, ai_pricing_json,--# ai_input/output_cost_per_mtok, ai_max_cost_usd_per_day). scripts/import-from-live.js prints the--# values Live had (never the key).++# ai_input/output_cost_per_mtok, ai_max_cost_usd_per_day).#AI_ENABLED=true# anthropic | openai | openrouter | groq | ollama | (blank = OpenAI-shaped). Anthropic only when# AI_BASE_URL is blank or anthropic.com; any other base URL is treated as OpenAI-compatible.@@@ -92,7 +88,7 @@ AI_MODEL=#AI_LOCAL_LLM_MODEL=qwen2.5-1.5b-instruct#AI_LOCAL_LLM_TIMEOUT_MS=180000# media.analyze: file cap (also bounded by free temp disk minus the reserve), download timeout, scene threshold--#AI_MEDIA_ANALYSIS_DIR=/var/lib/openvibe-ai/media-tmp (default: media-tmp beside AI_DB_PATH; never a tmpfs)++#AI_MEDIA_ANALYSIS_DIR=/var/lib/openvibe-ai/media-tmp (default: data/media-tmp; never a tmpfs)#AI_MEDIA_ANALYSIS_MAX_BYTES=4294967296#AI_MEDIA_ANALYSIS_DISK_RESERVE_BYTES=3221225472#AI_MEDIA_ANALYSIS_FETCH_TIMEOUT_MS=1200000@@@ -315,15 +315,13 @@ Production deploys with `sudo ovhost deploy ai` on the host (strategy `git-checkfast-forward `/opt/openvibe.ai`, install on a lockfile change, restart, wait for `/api/ready`).The unit is `openvibe-ai.service` on `127.0.0.1:4700`, the env file `/etc/openvibe/ai.env`. The database is`ov_ai` on the host's data role (`sudo /opt/openvibe.host/roles/data/add-service.sh ai` writes its settings); the--release migrates it at boot. The one-time move from SQLite is `scripts/migrate-to-postgres.js` (openvibe-sdk--`runSqliteMigration`, with a `--pglite` rehearsal mode), run while the service is stopped; the old--`/var/lib/openvibe-ai/ai.db` stays read-only for 7 days as the rollback. The local model++release migrates it at boot. The local modelruns as `openvibe-llm.service` on 127.0.0.1:8090 ([Local model](#local-model)).Rollback: ovhost puts the previous sha back by itself when `/api/ready` does not answer 2xx after therestart; afterwards `sudo ovhost rollback ai --to <sha>`. Migrations only add tables and columns.Deployed: `/opt/openvibe.ai`, env `/etc/openvibe/ai.env` (0600), unit `deploy/systemd/openvibe-ai.service`--(`StateDirectory=openvibe-ai`; database `ov_ai` on the host's data role, the SQLite file `/var/lib/openvibe-ai/ai.db` before the switch), principal `ai`. The nginx vhost++(`StateDirectory=openvibe-ai`; database `ov_ai` on the host's data role), principal `ai`. The nginx vhost`deploy/nginx/ai.openvibe.network.conf` (health/ready, `/`, `/robots.txt`, the operator console`/console` and its sign-in `/auth/` public; the API is host-local) is not installed:`ai.openvibe.network` still serves the Sites placeholder. The console also needs@@@ -345,7 +343,7 @@ Deployed: `/opt/openvibe.ai`, env `/etc/openvibe/ai.env` (0600), unit `deploy/sy- No fallback is declared on any production route, so an outage of the one real provider fails everyLive AI feature. On 2026-09-23 `live.*` and `network.site_copy` outputs carried no citations or gaps(0 citation rows); runs record grounding (cited sources and named gaps) since 2026-09-25.--- The host has a backup of `ai.db` but no restore drill has run for it; `/metrics` is built but not++- The host has a backup of `ov_ai` but no restore drill has run for it; `/metrics` is built but notdeployed yet.## Launch rule@@@ -4,7 +4,7 @@"code": true,"wave": "W13","plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",-- "runtime": "node 22, express, better-sqlite3",++ "runtime": "node 22, express, postgresql","port": 4700,"domain": "ai.openvibe.network still serves the OpenVibe.Sites placeholder; the service runs on openvibe-ovh (/opt/openvibe.ai, unit openvibe-ai) on 127.0.0.1:4700, loopback only, used by OpenVibe.Live and OpenVibe.Network","contracts": "openvibe-contracts v0.76.0",@@@ -26,9 +26,10 @@ ProtectSystem=strictProtectHome=read-only# whisper.cpp (live.media.transcribe) runs as a child process; it reads its binary and models from# WHISPER_BIN / WHISPER_MODEL* (defaults under ~/whisper.cpp, readable with ProtectHome=read-only).--# The code tree stays read-only; the database lives in /var/lib/openvibe-ai.++# The code tree stays read-only; the media work directory lives in /var/lib/openvibe-ai.StateDirectory=openvibe-ai--Environment=AI_DB_PATH=/var/lib/openvibe-ai/ai.db++# media.analyze's work directory under StateDirectory (writable; the code tree is read-only).++Environment=AI_MEDIA_ANALYSIS_DIR=/var/lib/openvibe-ai/media-tmp[Install]WantedBy=multi-user.target@@@ -10,7 +10,6 @@"license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",-- "better-sqlite3": "^11.10.0","dotenv": "^16.4.7","express": "^4.21.2","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",@@@ -476,7 +475,9 @@"url": "https://feross.org/support"}],-- "license": "MIT"++ "license": "MIT",++ "optional": true,++ "peer": true},"node_modules/better-sqlite3": {"version": "11.10.0",@@@ -484,6 +485,8 @@"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==","hasInstallScript": true,"license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"bindings": "^1.5.0","prebuild-install": "^7.1.1"@@@ -494,6 +497,8 @@"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz","integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==","license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"file-uri-to-path": "1.0.0"}@@@ -503,6 +508,8 @@"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz","integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==","license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"buffer": "^5.5.0","inherits": "^2.0.4",@@@ -552,6 +559,8 @@}],"license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"base64-js": "^1.3.1","ieee754": "^1.1.13"@@@ -605,7 +614,9 @@"version": "1.1.4","resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz","integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",-- "license": "ISC"++ "license": "ISC",++ "optional": true,++ "peer": true},"node_modules/color": {"version": "4.2.3",@@@ -702,6 +713,8 @@"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz","integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==","license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"mimic-response": "^3.1.0"},@@@ -717,6 +730,8 @@"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz","integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==","license": "MIT",++ "optional": true,++ "peer": true,"engines": {"node": ">=4.0.0"}@@@ -745,6 +760,7 @@"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz","integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==","license": "Apache-2.0",++ "optional": true,"engines": {"node": ">=8"}@@@ -804,6 +820,8 @@"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz","integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==","license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"once": "^1.4.0"}@@@ -858,6 +876,8 @@"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz","integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==","license": "(MIT OR WTFPL)",++ "optional": true,++ "peer": true,"engines": {"node": ">=6"}@@@ -934,7 +954,9 @@"version": "1.0.0","resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz","integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",-- "license": "MIT"++ "license": "MIT",++ "optional": true,++ "peer": true},"node_modules/finalhandler": {"version": "1.3.2",@@@ -976,7 +998,9 @@"version": "1.0.0","resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz","integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",-- "license": "MIT"++ "license": "MIT",++ "optional": true,++ "peer": true},"node_modules/function-bind": {"version": "1.1.2",@@@ -1028,7 +1052,9 @@"version": "0.0.0","resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz","integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",-- "license": "MIT"++ "license": "MIT",++ "optional": true,++ "peer": true},"node_modules/gopd": {"version": "1.2.0",@@@ -1116,7 +1142,9 @@"url": "https://feross.org/support"}],-- "license": "BSD-3-Clause"++ "license": "BSD-3-Clause",++ "optional": true,++ "peer": true},"node_modules/inherits": {"version": "2.0.4",@@@ -1128,7 +1156,9 @@"version": "1.3.8","resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz","integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",-- "license": "ISC"++ "license": "ISC",++ "optional": true,++ "peer": true},"node_modules/ipaddr.js": {"version": "1.9.1",@@@ -1317,6 +1347,8 @@"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz","integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==","license": "MIT",++ "optional": true,++ "peer": true,"engines": {"node": ">=10"},@@@ -1329,6 +1361,8 @@"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz","integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==","license": "MIT",++ "optional": true,++ "peer": true,"funding": {"url": "https://github.com/sponsors/ljharb"}@@@ -1337,7 +1371,9 @@"version": "0.5.3","resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz","integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",-- "license": "MIT"++ "license": "MIT",++ "optional": true,++ "peer": true},"node_modules/ms": {"version": "2.0.0",@@@ -1349,7 +1385,9 @@"version": "2.0.0","resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz","integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",-- "license": "MIT"++ "license": "MIT",++ "optional": true,++ "peer": true},"node_modules/negotiator": {"version": "0.6.3",@@@ -1365,6 +1403,8 @@"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz","integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==","license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"semver": "^7.3.5"},@@@ -1401,6 +1441,8 @@"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz","integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==","license": "ISC",++ "optional": true,++ "peer": true,"dependencies": {"wrappy": "1"}@@@ -1608,6 +1650,8 @@"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==","deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.","license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"detect-libc": "^2.0.0","expand-template": "^2.0.3",@@@ -1651,6 +1695,8 @@"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz","integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==","license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"end-of-stream": "^1.1.0","once": "^1.3.1"@@@ -1701,6 +1747,8 @@"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz","integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==","license": "(BSD-2-Clause OR MIT OR Apache-2.0)",++ "optional": true,++ "peer": true,"dependencies": {"deep-extend": "^0.6.0","ini": "~1.3.0",@@@ -1716,6 +1764,8 @@"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz","integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==","license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"inherits": "^2.0.3","string_decoder": "^1.1.1",@@@ -1953,7 +2003,9 @@"url": "https://feross.org/support"}],-- "license": "MIT"++ "license": "MIT",++ "optional": true,++ "peer": true},"node_modules/simple-get": {"version": "4.0.1",@@@ -1974,6 +2026,8 @@}],"license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"decompress-response": "^6.0.0","once": "^1.3.1",@@@ -2013,6 +2067,8 @@"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz","integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==","license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"safe-buffer": "~5.2.0"}@@@ -2022,6 +2078,8 @@"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz","integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==","license": "MIT",++ "optional": true,++ "peer": true,"engines": {"node": ">=0.10.0"}@@@ -2031,6 +2089,8 @@"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz","integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==","license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"chownr": "^1.1.1","mkdirp-classic": "^0.5.2",@@@ -2043,6 +2103,8 @@"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz","integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==","license": "MIT",++ "optional": true,++ "peer": true,"dependencies": {"bl": "^4.0.3","end-of-stream": "^1.4.1",@@@ -2075,6 +2137,8 @@"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz","integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==","license": "Apache-2.0",++ "optional": true,++ "peer": true,"dependencies": {"safe-buffer": "^5.0.1"},@@@ -2108,7 +2172,9 @@"version": "1.0.2","resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz","integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",-- "license": "MIT"++ "license": "MIT",++ "optional": true,++ "peer": true},"node_modules/utils-merge": {"version": "1.0.1",@@@ -2132,7 +2198,9 @@"version": "1.0.2","resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz","integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",-- "license": "ISC"++ "license": "ISC",++ "optional": true,++ "peer": true},"node_modules/xtend": {"version": "4.0.2",@@@ -22,7 +22,6 @@"repository": "github:OpenVibers/OpenVibe.AI","dependencies": {"ajv": "^8.17.1",-- "better-sqlite3": "^11.10.0","dotenv": "^16.4.7","express": "^4.21.2","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",ddeleted file mode 100644@@@ -1,26 +0,0 @@--#!/usr/bin/env node--'use strict';--/**-- * The one-time move of AI's SQLite database (AI_DB_PATH) into its PostgreSQL schema (ADR-035; the procedure is-- * openvibe-sdk docs/migrating-to-postgresql.md, section 6, carried out by openvibe-sdk/db runSqliteMigration).-- *-- * node scripts/migrate-to-postgres.js [--sqlite <file>] [--pglite] [--json]-- *-- * Applies migrations/ as the owner (DATABASE_DIRECT_URL), copies every table into emptied tables, verifies row counts-- * and checksums, and exits 1 unless everything verified. The SQLite file is opened read-only. Every table keeps its-- * name and columns.-- */--require('dotenv').config();--const { runSqliteMigration } = require('openvibe-sdk/db');--const configLib = require('../server/config');--const { MIGRATIONS } = require('../server/db');----const TABLES = {};----if (require.main === module) {-- const config = (configLib.load || configLib.loadConfig)();-- runSqliteMigration({ service: 'ai', sqlite: config.dbPath, directUrl: config.db.directUrl, migrations: MIGRATIONS, tables: TABLES })-- .then((code) => process.exit(code), (err) => { console.error(`migrate-to-postgres failed: ${err.message}`); process.exit(1); });--}----module.exports = { TABLES };@@@ -61,8 +61,6 @@ function load(env = process.env) {// PostgreSQL (ADR-035): DATABASE_URL serves (PgBouncer), DATABASE_DIRECT_URL migrates (owner role).db: { url: env.DATABASE_URL || '', directUrl: env.DATABASE_DIRECT_URL || '' },-- // The SQLite file of releases before the switch: read once by scripts/migrate-to-postgres.js.-- dbPath: env.AI_DB_PATH || './data/ai.db',// ── Operator console (server/console): OpenVibe.Network SSO (authorization code + PKCE S256// as OAuth client `ai`), for Network staff only (README "Operator console"). Both secrets are@@@ -107,8 +105,8 @@ function load(env = process.env) {// media.analyze (server/workflows/media-analysis.js): the file cap (also bounded by free disk minus a reserve),// the download timeout, the scene-change threshold (scdet, 0-100) and the tools.mediaAnalysis: {-- // On disk beside the database, not os.tmpdir(): a tmpfs /tmp is memory, and a VOD is gigabytes.-- workDir: env.AI_MEDIA_ANALYSIS_DIR || require('path').join(require('path').dirname(env.AI_DB_PATH || './data/ai.db'), 'media-tmp'),++ // On disk under the service's data directory, not os.tmpdir(): a tmpfs /tmp is memory, and a VOD is gigabytes.++ workDir: env.AI_MEDIA_ANALYSIS_DIR || 'data/media-tmp',maxBytes: int(env.AI_MEDIA_ANALYSIS_MAX_BYTES, 4 * 1024 * 1024 * 1024),reserveBytes: int(env.AI_MEDIA_ANALYSIS_DISK_RESERVE_BYTES, 3 * 1024 * 1024 * 1024),fetchTimeoutMs: int(env.AI_MEDIA_ANALYSIS_FETCH_TIMEOUT_MS, 20 * 60000),@@@ -36,7 +36,6 @@ async function start({ config, db: givenDb = null, clock = { now: () => Date.nowrequire('./events').init(db, { log });// ai.preferences (read) and ai.usage_summary (written): Network user modules (server/user-modules.js).const userModules = require('./user-modules').createUserModules({ db, config, env, fetchImpl, clock, log });-- userModules.ensureSchema();// A person's own provider keys (WS-O task 2): stored by the service holding their consent, used by their runs only.const credentials = require('./credentials').createCredentials({ db, config, clock });const runs = createRuns({ db, registry, engine, cache, quotas, config, clock, log, userModules, credentials });@@@ -3,7 +3,7 @@* Track O: truthful readiness for GET /api/ready and the AI gauges on GET /metrics* (openvibe-shared/ready and openvibe-shared/metrics).*-- * db required a real query on AI's SQLite (the provider registry answers)++ * db required a real query on AI's PostgreSQL database (the provider registry answers)* workflows required at least one active workflow: without one there is nothing to run* network_jwks required the Network signing key has loaded. Every endpoint but health/ready* needs a verified service token, so without it AI serves nothing@@@ -66,10 +66,6 @@ function createUserModules({ db, config, env = process.env, fetchImpl = globalThreturn data;}-- function ensureSchema() {-- // ai_module_pushes is in migrations/0001_initial.sql (ADR-035).-- }--/** ai.usage_summary for one person over the 30 days before now, or null when there were no runs. */async function summarize(subject, now = clock.now()) {const since = new Date(now - 30 * DAY_MS).toISOString();@@@ -130,7 +126,6 @@ function createUserModules({ db, config, env = process.env, fetchImpl = globalThfunction start() {if (!enabled || timers.length) return false;-- ensureSchema();const safe = (fn) => () => { fn().catch((err) => { stats.lastError = err.message; }); };timers = [setInterval(safe(async () => await scan()), 5 * 60 * 1000), setInterval(safe(async () => await refresh()), DAY_MS), setTimeout(safe(async () => await refresh()), 3 * 60 * 1000)];for (const t of timers) t.unref?.();@@@ -138,7 +133,7 @@ function createUserModules({ db, config, env = process.env, fetchImpl = globalTh}function stop() { for (const t of timers) { clearInterval(t); clearTimeout(t); } timers = []; }-- return { enabled, preferencesFor, ensureSchema, summarize, push, scan, refresh, start, stop, stats: () => ({ enabled, ...stats }) };++ return { enabled, preferencesFor, summarize, push, scan, refresh, start, stop, stats: () => ({ enabled, ...stats }) };}/** The system-prompt lines a person's ai.preferences add ('' when none). */@@@ -42,14 +42,14 @@ function tmpDir() {}/** Boot the service. `env` goes through config.load() and is also what secrets resolve against. */--// A boot on a dir used before is a restart on the same database (as the SQLite file was): its database stays open.++// A boot on a dir used before is a restart on the same database: its database stays open.const restartable = new Map();async function boot({ env = {}, dir, clock, fetchImpl, credentialFetch } = {}) {const reuse = dir !== undefined;if (dir === undefined) dir = tmpDir();const fullEnv = {-- NODE_ENV: 'test', PORT: '0', AI_DB_PATH: path.join(dir, 'ai.db'), OV_NETWORK_PUBLIC_KEY: publicKey,++ NODE_ENV: 'test', PORT: '0', OV_NETWORK_PUBLIC_KEY: publicKey, AI_MEDIA_ANALYSIS_DIR: path.join(dir, 'media-tmp'),AI_PROVIDER_RETRY_DELAY_MS: '5', AI_QUOTA_SERVICE_RPM: '0', AI_QUOTA_SERVICE_RPD: '0',...env,};@@@ -1,7 +1,7 @@#!/usr/bin/env node/*** Runs every test in test/ — the files named *.test.js — each in its own process, and fails-- * if any of them fails. They use temp SQLite databases, a generated signing key, the deterministic++ * if any of them fails. They use temp databases (PGlite by default, PostgreSQL with npm run test:pg), a generated signing key, the deterministic* stub provider and local HTTP servers on random ports; none of them needs the network, a paid* key or a running OpenVibe.Network.*@@@ -75,7 +75,6 @@ t.test('runs for someone without preferences, or for nobody, are untouched', asyt.test('ai.usage_summary: 30-day runs and tokens per person, written only when changed', async () => {const mods = require('../server/user-modules').createUserModules({ db: h.db, config: h.config || { networkInternalUrl: h.env.OV_NETWORK_INTERNAL_URL }, env: h.env, log: {} });-- mods.ensureSchema();const s = await mods.summarize(A.id);assert.strictEqual(s.runs_30d, 2); assert.strictEqual(s.tokens_30d, 80);assert.deepStrictEqual(s.by_service, { live: 2 });
@@ -10,16 +10,13 @@ HOST=127.0.0.1
@@ -10,16 +10,13 @@ HOST=127.0.0.1
NODE_ENV=development
NODE_ENV=development
BASE_URL=http://localhost:4700
BASE_URL=http://localhost:4700
-# SQLite (production unit: /var/lib/openvibe-ai/ai.db via StateDirectory=openvibe-ai)
# PostgreSQL (ADR-035), written by OpenVibe.Host roles/data/add-service.sh ai: the runtime role through PgBouncer,
# PostgreSQL (ADR-035), written by OpenVibe.Host roles/data/add-service.sh ai: the runtime role through PgBouncer,
-# the owner on a direct connection (migrations and the one-time import), and Valkey (shared limit counters).
+# the owner on a direct connection (migrations), and Valkey (shared limit counters).
# Without DATABASE_URL, development uses an embedded PGlite database in data/pglite.
# Without DATABASE_URL, development uses an embedded PGlite database in data/pglite.
DATABASE_URL=
DATABASE_URL=
DATABASE_DIRECT_URL=
DATABASE_DIRECT_URL=
VALKEY_URL=
VALKEY_URL=
VALKEY_PREFIX=
VALKEY_PREFIX=
-# The SQLite file of releases before PostgreSQL: read once by scripts/migrate-to-postgres.js.
-AI_DB_PATH=./data/ai.db
# Identity: OpenVibe.Network signs the service tokens callers present (audience openvibe.ai).
# Identity: OpenVibe.Network signs the service tokens callers present (audience openvibe.ai).
# The public key is fetched from <Network>/api/.well-known/jwks; OV_NETWORK_PUBLIC_KEY pins a PEM instead.
# The public key is fetched from <Network>/api/.well-known/jwks; OV_NETWORK_PUBLIC_KEY pins a PEM instead.
@@ -52,8 +49,7 @@ OV_NETWORK_INTERNAL_URL=http://127.0.0.1:4000
@@ -52,8 +49,7 @@ OV_NETWORK_INTERNAL_URL=http://127.0.0.1:4000
# ── The shared provider — the same names as OpenVibe.Live's admin AI settings ──
# ── The shared provider — the same names as OpenVibe.Live's admin AI settings ──
# (ai_enabled, ai_provider, ai_base_url, ai_api_key, ai_model, ai_model_<role>, ai_pricing_json,
# (ai_enabled, ai_provider, ai_base_url, ai_api_key, ai_model, ai_model_<role>, ai_pricing_json,
-# ai_input/output_cost_per_mtok, ai_max_cost_usd_per_day). scripts/import-from-live.js prints the
-# values Live had (never the key).
+# ai_input/output_cost_per_mtok, ai_max_cost_usd_per_day).
#AI_ENABLED=true
#AI_ENABLED=true
# anthropic | openai | openrouter | groq | ollama | (blank = OpenAI-shaped). Anthropic only when
# anthropic | openai | openrouter | groq | ollama | (blank = OpenAI-shaped). Anthropic only when
# AI_BASE_URL is blank or anthropic.com; any other base URL is treated as OpenAI-compatible.
# AI_BASE_URL is blank or anthropic.com; any other base URL is treated as OpenAI-compatible.
@@ -92,7 +88,7 @@ AI_MODEL=
@@ -92,7 +88,7 @@ AI_MODEL=
#AI_LOCAL_LLM_MODEL=qwen2.5-1.5b-instruct
#AI_LOCAL_LLM_MODEL=qwen2.5-1.5b-instruct
#AI_LOCAL_LLM_TIMEOUT_MS=180000
#AI_LOCAL_LLM_TIMEOUT_MS=180000
# media.analyze: file cap (also bounded by free temp disk minus the reserve), download timeout, scene threshold
# media.analyze: file cap (also bounded by free temp disk minus the reserve), download timeout, scene threshold
-#AI_MEDIA_ANALYSIS_DIR=/var/lib/openvibe-ai/media-tmp (default: media-tmp beside AI_DB_PATH; never a tmpfs)
+#AI_MEDIA_ANALYSIS_DIR=/var/lib/openvibe-ai/media-tmp (default: data/media-tmp; never a tmpfs)
#AI_MEDIA_ANALYSIS_MAX_BYTES=4294967296
#AI_MEDIA_ANALYSIS_MAX_BYTES=4294967296
#AI_MEDIA_ANALYSIS_DISK_RESERVE_BYTES=3221225472
#AI_MEDIA_ANALYSIS_DISK_RESERVE_BYTES=3221225472
#AI_MEDIA_ANALYSIS_FETCH_TIMEOUT_MS=1200000
#AI_MEDIA_ANALYSIS_FETCH_TIMEOUT_MS=1200000
@@ -315,15 +315,13 @@ Production deploys with `sudo ovhost deploy ai` on the host (strategy `git-check
@@ -315,15 +315,13 @@ Production deploys with `sudo ovhost deploy ai` on the host (strategy `git-check
fast-forward `/opt/openvibe.ai`, install on a lockfile change, restart, wait for `/api/ready`).
fast-forward `/opt/openvibe.ai`, install on a lockfile change, restart, wait for `/api/ready`).
The unit is `openvibe-ai.service` on `127.0.0.1:4700`, the env file `/etc/openvibe/ai.env`. The database is
The unit is `openvibe-ai.service` on `127.0.0.1:4700`, the env file `/etc/openvibe/ai.env`. The database is
`ov_ai` on the host's data role (`sudo /opt/openvibe.host/roles/data/add-service.sh ai` writes its settings); the
`ov_ai` on the host's data role (`sudo /opt/openvibe.host/roles/data/add-service.sh ai` writes its settings); the
-release migrates it at boot. The one-time move from SQLite is `scripts/migrate-to-postgres.js` (openvibe-sdk
-`runSqliteMigration`, with a `--pglite` rehearsal mode), run while the service is stopped; the old
-`/var/lib/openvibe-ai/ai.db` stays read-only for 7 days as the rollback. The local model
+release migrates it at boot. The local model
runs as `openvibe-llm.service` on 127.0.0.1:8090 ([Local model](#local-model)).
runs as `openvibe-llm.service` on 127.0.0.1:8090 ([Local model](#local-model)).
Rollback: ovhost puts the previous sha back by itself when `/api/ready` does not answer 2xx after the
Rollback: ovhost puts the previous sha back by itself when `/api/ready` does not answer 2xx after the
restart; afterwards `sudo ovhost rollback ai --to <sha>`. Migrations only add tables and columns.
restart; afterwards `sudo ovhost rollback ai --to <sha>`. Migrations only add tables and columns.
Deployed: `/opt/openvibe.ai`, env `/etc/openvibe/ai.env` (0600), unit `deploy/systemd/openvibe-ai.service`
Deployed: `/opt/openvibe.ai`, env `/etc/openvibe/ai.env` (0600), unit `deploy/systemd/openvibe-ai.service`
-(`StateDirectory=openvibe-ai`; database `ov_ai` on the host's data role, the SQLite file `/var/lib/openvibe-ai/ai.db` before the switch), principal `ai`. The nginx vhost
+(`StateDirectory=openvibe-ai`; database `ov_ai` on the host's data role), principal `ai`. The nginx vhost
`deploy/nginx/ai.openvibe.network.conf` (health/ready, `/`, `/robots.txt`, the operator console
`deploy/nginx/ai.openvibe.network.conf` (health/ready, `/`, `/robots.txt`, the operator console
`/console` and its sign-in `/auth/` public; the API is host-local) is not installed:
`/console` and its sign-in `/auth/` public; the API is host-local) is not installed:
`ai.openvibe.network` still serves the Sites placeholder. The console also needs
`ai.openvibe.network` still serves the Sites placeholder. The console also needs
@@ -345,7 +343,7 @@ Deployed: `/opt/openvibe.ai`, env `/etc/openvibe/ai.env` (0600), unit `deploy/sy
@@ -345,7 +343,7 @@ Deployed: `/opt/openvibe.ai`, env `/etc/openvibe/ai.env` (0600), unit `deploy/sy
- No fallback is declared on any production route, so an outage of the one real provider fails every
- No fallback is declared on any production route, so an outage of the one real provider fails every
Live AI feature. On 2026-09-23 `live.*` and `network.site_copy` outputs carried no citations or gaps
Live AI feature. On 2026-09-23 `live.*` and `network.site_copy` outputs carried no citations or gaps
(0 citation rows); runs record grounding (cited sources and named gaps) since 2026-09-25.
(0 citation rows); runs record grounding (cited sources and named gaps) since 2026-09-25.
-- The host has a backup of `ai.db` but no restore drill has run for it; `/metrics` is built but not
+- The host has a backup of `ov_ai` but no restore drill has run for it; `/metrics` is built but not
deployed yet.
deployed yet.
## Launch rule
## Launch rule
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
"code": true,
"code": true,
"wave": "W13",
"wave": "W13",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
- "runtime": "node 22, express, better-sqlite3",
+ "runtime": "node 22, express, postgresql",
"port": 4700,
"port": 4700,
"domain": "ai.openvibe.network still serves the OpenVibe.Sites placeholder; the service runs on openvibe-ovh (/opt/openvibe.ai, unit openvibe-ai) on 127.0.0.1:4700, loopback only, used by OpenVibe.Live and OpenVibe.Network",
"domain": "ai.openvibe.network still serves the OpenVibe.Sites placeholder; the service runs on openvibe-ovh (/opt/openvibe.ai, unit openvibe-ai) on 127.0.0.1:4700, loopback only, used by OpenVibe.Live and OpenVibe.Network",
"contracts": "openvibe-contracts v0.76.0",
"contracts": "openvibe-contracts v0.76.0",
@@ -26,9 +26,10 @@ ProtectSystem=strict
@@ -26,9 +26,10 @@ ProtectSystem=strict
ProtectHome=read-only
ProtectHome=read-only
# whisper.cpp (live.media.transcribe) runs as a child process; it reads its binary and models from
# whisper.cpp (live.media.transcribe) runs as a child process; it reads its binary and models from
# WHISPER_BIN / WHISPER_MODEL* (defaults under ~/whisper.cpp, readable with ProtectHome=read-only).
# WHISPER_BIN / WHISPER_MODEL* (defaults under ~/whisper.cpp, readable with ProtectHome=read-only).
-# The code tree stays read-only; the database lives in /var/lib/openvibe-ai.
+# The code tree stays read-only; the media work directory lives in /var/lib/openvibe-ai.
StateDirectory=openvibe-ai
StateDirectory=openvibe-ai
-Environment=AI_DB_PATH=/var/lib/openvibe-ai/ai.db
+# media.analyze's work directory under StateDirectory (writable; the code tree is read-only).
+Environment=AI_MEDIA_ANALYSIS_DIR=/var/lib/openvibe-ai/media-tmp
[Install]
[Install]
WantedBy=multi-user.target
WantedBy=multi-user.target
@@ -10,7 +10,6 @@
@@ -10,7 +10,6 @@
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
- "better-sqlite3": "^11.10.0",
"dotenv": "^16.4.7",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express": "^4.21.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
@@ -476,7 +475,9 @@
@@ -476,7 +475,9 @@
"url": "https://feross.org/support"
"url": "https://feross.org/support"
}
}
],
],
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "peer": true
},
},
"node_modules/better-sqlite3": {
"node_modules/better-sqlite3": {
"version": "11.10.0",
"version": "11.10.0",
@@ -484,6 +485,8 @@
@@ -484,6 +485,8 @@
"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
"integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==",
"hasInstallScript": true,
"hasInstallScript": true,
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"bindings": "^1.5.0",
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
"prebuild-install": "^7.1.1"
@@ -494,6 +497,8 @@
@@ -494,6 +497,8 @@
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"file-uri-to-path": "1.0.0"
"file-uri-to-path": "1.0.0"
}
}
@@ -503,6 +508,8 @@
@@ -503,6 +508,8 @@
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"buffer": "^5.5.0",
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"inherits": "^2.0.4",
@@ -552,6 +559,8 @@
@@ -552,6 +559,8 @@
}
}
],
],
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"base64-js": "^1.3.1",
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
"ieee754": "^1.1.13"
@@ -605,7 +614,9 @@
@@ -605,7 +614,9 @@
"version": "1.1.4",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
- "license": "ISC"
+ "license": "ISC",
+ "optional": true,
+ "peer": true
},
},
"node_modules/color": {
"node_modules/color": {
"version": "4.2.3",
"version": "4.2.3",
@@ -702,6 +713,8 @@
@@ -702,6 +713,8 @@
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"mimic-response": "^3.1.0"
"mimic-response": "^3.1.0"
},
},
@@ -717,6 +730,8 @@
@@ -717,6 +730,8 @@
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"engines": {
"engines": {
"node": ">=4.0.0"
"node": ">=4.0.0"
}
}
@@ -745,6 +760,7 @@
@@ -745,6 +760,7 @@
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"license": "Apache-2.0",
+ "optional": true,
"engines": {
"engines": {
"node": ">=8"
"node": ">=8"
}
}
@@ -804,6 +820,8 @@
@@ -804,6 +820,8 @@
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"once": "^1.4.0"
"once": "^1.4.0"
}
}
@@ -858,6 +876,8 @@
@@ -858,6 +876,8 @@
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"license": "(MIT OR WTFPL)",
"license": "(MIT OR WTFPL)",
+ "optional": true,
+ "peer": true,
"engines": {
"engines": {
"node": ">=6"
"node": ">=6"
}
}
@@ -934,7 +954,9 @@
@@ -934,7 +954,9 @@
"version": "1.0.0",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "peer": true
},
},
"node_modules/finalhandler": {
"node_modules/finalhandler": {
"version": "1.3.2",
"version": "1.3.2",
@@ -976,7 +998,9 @@
@@ -976,7 +998,9 @@
"version": "1.0.0",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "peer": true
},
},
"node_modules/function-bind": {
"node_modules/function-bind": {
"version": "1.1.2",
"version": "1.1.2",
@@ -1028,7 +1052,9 @@
@@ -1028,7 +1052,9 @@
"version": "0.0.0",
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "peer": true
},
},
"node_modules/gopd": {
"node_modules/gopd": {
"version": "1.2.0",
"version": "1.2.0",
@@ -1116,7 +1142,9 @@
@@ -1116,7 +1142,9 @@
"url": "https://feross.org/support"
"url": "https://feross.org/support"
}
}
],
],
- "license": "BSD-3-Clause"
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "peer": true
},
},
"node_modules/inherits": {
"node_modules/inherits": {
"version": "2.0.4",
"version": "2.0.4",
@@ -1128,7 +1156,9 @@
@@ -1128,7 +1156,9 @@
"version": "1.3.8",
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
- "license": "ISC"
+ "license": "ISC",
+ "optional": true,
+ "peer": true
},
},
"node_modules/ipaddr.js": {
"node_modules/ipaddr.js": {
"version": "1.9.1",
"version": "1.9.1",
@@ -1317,6 +1347,8 @@
@@ -1317,6 +1347,8 @@
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"engines": {
"engines": {
"node": ">=10"
"node": ">=10"
},
},
@@ -1329,6 +1361,8 @@
@@ -1329,6 +1361,8 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"funding": {
"funding": {
"url": "https://github.com/sponsors/ljharb"
"url": "https://github.com/sponsors/ljharb"
}
}
@@ -1337,7 +1371,9 @@
@@ -1337,7 +1371,9 @@
"version": "0.5.3",
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "peer": true
},
},
"node_modules/ms": {
"node_modules/ms": {
"version": "2.0.0",
"version": "2.0.0",
@@ -1349,7 +1385,9 @@
@@ -1349,7 +1385,9 @@
"version": "2.0.0",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "peer": true
},
},
"node_modules/negotiator": {
"node_modules/negotiator": {
"version": "0.6.3",
"version": "0.6.3",
@@ -1365,6 +1403,8 @@
@@ -1365,6 +1403,8 @@
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz",
"integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==",
"integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"semver": "^7.3.5"
"semver": "^7.3.5"
},
},
@@ -1401,6 +1441,8 @@
@@ -1401,6 +1441,8 @@
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"license": "ISC",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"wrappy": "1"
"wrappy": "1"
}
}
@@ -1608,6 +1650,8 @@
@@ -1608,6 +1650,8 @@
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"detect-libc": "^2.0.0",
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"expand-template": "^2.0.3",
@@ -1651,6 +1695,8 @@
@@ -1651,6 +1695,8 @@
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"end-of-stream": "^1.1.0",
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
"once": "^1.3.1"
@@ -1701,6 +1747,8 @@
@@ -1701,6 +1747,8 @@
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"deep-extend": "^0.6.0",
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"ini": "~1.3.0",
@@ -1716,6 +1764,8 @@
@@ -1716,6 +1764,8 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"inherits": "^2.0.3",
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"string_decoder": "^1.1.1",
@@ -1953,7 +2003,9 @@
@@ -1953,7 +2003,9 @@
"url": "https://feross.org/support"
"url": "https://feross.org/support"
}
}
],
],
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "peer": true
},
},
"node_modules/simple-get": {
"node_modules/simple-get": {
"version": "4.0.1",
"version": "4.0.1",
@@ -1974,6 +2026,8 @@
@@ -1974,6 +2026,8 @@
}
}
],
],
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"decompress-response": "^6.0.0",
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"once": "^1.3.1",
@@ -2013,6 +2067,8 @@
@@ -2013,6 +2067,8 @@
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"safe-buffer": "~5.2.0"
"safe-buffer": "~5.2.0"
}
}
@@ -2022,6 +2078,8 @@
@@ -2022,6 +2078,8 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"engines": {
"engines": {
"node": ">=0.10.0"
"node": ">=0.10.0"
}
}
@@ -2031,6 +2089,8 @@
@@ -2031,6 +2089,8 @@
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz",
"integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==",
"integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"chownr": "^1.1.1",
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"mkdirp-classic": "^0.5.2",
@@ -2043,6 +2103,8 @@
@@ -2043,6 +2103,8 @@
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"bl": "^4.0.3",
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"end-of-stream": "^1.4.1",
@@ -2075,6 +2137,8 @@
@@ -2075,6 +2137,8 @@
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"license": "Apache-2.0",
+ "optional": true,
+ "peer": true,
"dependencies": {
"dependencies": {
"safe-buffer": "^5.0.1"
"safe-buffer": "^5.0.1"
},
},
@@ -2108,7 +2172,9 @@
@@ -2108,7 +2172,9 @@
"version": "1.0.2",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "peer": true
},
},
"node_modules/utils-merge": {
"node_modules/utils-merge": {
"version": "1.0.1",
"version": "1.0.1",
@@ -2132,7 +2198,9 @@
@@ -2132,7 +2198,9 @@
"version": "1.0.2",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "license": "ISC"
+ "license": "ISC",
+ "optional": true,
+ "peer": true
},
},
"node_modules/xtend": {
"node_modules/xtend": {
"version": "4.0.2",
"version": "4.0.2",
@@ -22,7 +22,6 @@
@@ -22,7 +22,6 @@
"repository": "github:OpenVibers/OpenVibe.AI",
"repository": "github:OpenVibers/OpenVibe.AI",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
- "better-sqlite3": "^11.10.0",
"dotenv": "^16.4.7",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express": "^4.21.2",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.76.0",
deleted file mode 100644
deleted file mode 100644
@@ -1,26 +0,0 @@
@@ -1,26 +0,0 @@
-#!/usr/bin/env node
-'use strict';
-/**
- * The one-time move of AI's SQLite database (AI_DB_PATH) into its PostgreSQL schema (ADR-035; the procedure is
- * openvibe-sdk docs/migrating-to-postgresql.md, section 6, carried out by openvibe-sdk/db runSqliteMigration).
- *
- * node scripts/migrate-to-postgres.js [--sqlite <file>] [--pglite] [--json]
- *
- * Applies migrations/ as the owner (DATABASE_DIRECT_URL), copies every table into emptied tables, verifies row counts
- * and checksums, and exits 1 unless everything verified. The SQLite file is opened read-only. Every table keeps its
- * name and columns.
- */
-require('dotenv').config();
-const { runSqliteMigration } = require('openvibe-sdk/db');
-const configLib = require('../server/config');
-const { MIGRATIONS } = require('../server/db');
-
-const TABLES = {};
-
-if (require.main === module) {
- const config = (configLib.load || configLib.loadConfig)();
- runSqliteMigration({ service: 'ai', sqlite: config.dbPath, directUrl: config.db.directUrl, migrations: MIGRATIONS, tables: TABLES })
- .then((code) => process.exit(code), (err) => { console.error(`migrate-to-postgres failed: ${err.message}`); process.exit(1); });
-}
-
-module.exports = { TABLES };
@@ -61,8 +61,6 @@ function load(env = process.env) {
@@ -61,8 +61,6 @@ function load(env = process.env) {
// PostgreSQL (ADR-035): DATABASE_URL serves (PgBouncer), DATABASE_DIRECT_URL migrates (owner role).
// PostgreSQL (ADR-035): DATABASE_URL serves (PgBouncer), DATABASE_DIRECT_URL migrates (owner role).
db: { url: env.DATABASE_URL || '', directUrl: env.DATABASE_DIRECT_URL || '' },
db: { url: env.DATABASE_URL || '', directUrl: env.DATABASE_DIRECT_URL || '' },
- // The SQLite file of releases before the switch: read once by scripts/migrate-to-postgres.js.
- dbPath: env.AI_DB_PATH || './data/ai.db',
// ── Operator console (server/console): OpenVibe.Network SSO (authorization code + PKCE S256
// ── Operator console (server/console): OpenVibe.Network SSO (authorization code + PKCE S256
// as OAuth client `ai`), for Network staff only (README "Operator console"). Both secrets are
// as OAuth client `ai`), for Network staff only (README "Operator console"). Both secrets are
@@ -107,8 +105,8 @@ function load(env = process.env) {
@@ -107,8 +105,8 @@ function load(env = process.env) {
// media.analyze (server/workflows/media-analysis.js): the file cap (also bounded by free disk minus a reserve),
// media.analyze (server/workflows/media-analysis.js): the file cap (also bounded by free disk minus a reserve),
// the download timeout, the scene-change threshold (scdet, 0-100) and the tools.
// the download timeout, the scene-change threshold (scdet, 0-100) and the tools.
mediaAnalysis: {
mediaAnalysis: {
- // On disk beside the database, not os.tmpdir(): a tmpfs /tmp is memory, and a VOD is gigabytes.
- workDir: env.AI_MEDIA_ANALYSIS_DIR || require('path').join(require('path').dirname(env.AI_DB_PATH || './data/ai.db'), 'media-tmp'),
+ // On disk under the service's data directory, not os.tmpdir(): a tmpfs /tmp is memory, and a VOD is gigabytes.
+ workDir: env.AI_MEDIA_ANALYSIS_DIR || 'data/media-tmp',
maxBytes: int(env.AI_MEDIA_ANALYSIS_MAX_BYTES, 4 * 1024 * 1024 * 1024),
maxBytes: int(env.AI_MEDIA_ANALYSIS_MAX_BYTES, 4 * 1024 * 1024 * 1024),
reserveBytes: int(env.AI_MEDIA_ANALYSIS_DISK_RESERVE_BYTES, 3 * 1024 * 1024 * 1024),
reserveBytes: int(env.AI_MEDIA_ANALYSIS_DISK_RESERVE_BYTES, 3 * 1024 * 1024 * 1024),
fetchTimeoutMs: int(env.AI_MEDIA_ANALYSIS_FETCH_TIMEOUT_MS, 20 * 60000),
fetchTimeoutMs: int(env.AI_MEDIA_ANALYSIS_FETCH_TIMEOUT_MS, 20 * 60000),
@@ -36,7 +36,6 @@ async function start({ config, db: givenDb = null, clock = { now: () => Date.now
@@ -36,7 +36,6 @@ async function start({ config, db: givenDb = null, clock = { now: () => Date.now
require('./events').init(db, { log });
require('./events').init(db, { log });
// ai.preferences (read) and ai.usage_summary (written): Network user modules (server/user-modules.js).
// ai.preferences (read) and ai.usage_summary (written): Network user modules (server/user-modules.js).
const userModules = require('./user-modules').createUserModules({ db, config, env, fetchImpl, clock, log });
const userModules = require('./user-modules').createUserModules({ db, config, env, fetchImpl, clock, log });
- userModules.ensureSchema();
// A person's own provider keys (WS-O task 2): stored by the service holding their consent, used by their runs only.
// A person's own provider keys (WS-O task 2): stored by the service holding their consent, used by their runs only.
const credentials = require('./credentials').createCredentials({ db, config, clock });
const credentials = require('./credentials').createCredentials({ db, config, clock });
const runs = createRuns({ db, registry, engine, cache, quotas, config, clock, log, userModules, credentials });
const runs = createRuns({ db, registry, engine, cache, quotas, config, clock, log, userModules, credentials });
@@ -3,7 +3,7 @@
@@ -3,7 +3,7 @@
* Track O: truthful readiness for GET /api/ready and the AI gauges on GET /metrics
* Track O: truthful readiness for GET /api/ready and the AI gauges on GET /metrics
* (openvibe-shared/ready and openvibe-shared/metrics).
* (openvibe-shared/ready and openvibe-shared/metrics).
*
*
- * db required a real query on AI's SQLite (the provider registry answers)
+ * db required a real query on AI's PostgreSQL database (the provider registry answers)
* workflows required at least one active workflow: without one there is nothing to run
* workflows required at least one active workflow: without one there is nothing to run
* network_jwks required the Network signing key has loaded. Every endpoint but health/ready
* network_jwks required the Network signing key has loaded. Every endpoint but health/ready
* needs a verified service token, so without it AI serves nothing
* needs a verified service token, so without it AI serves nothing
@@ -66,10 +66,6 @@ function createUserModules({ db, config, env = process.env, fetchImpl = globalTh
@@ -66,10 +66,6 @@ function createUserModules({ db, config, env = process.env, fetchImpl = globalTh
return data;
return data;
}
}
- function ensureSchema() {
- // ai_module_pushes is in migrations/0001_initial.sql (ADR-035).
- }
-
/** ai.usage_summary for one person over the 30 days before now, or null when there were no runs. */
/** ai.usage_summary for one person over the 30 days before now, or null when there were no runs. */
async function summarize(subject, now = clock.now()) {
async function summarize(subject, now = clock.now()) {
const since = new Date(now - 30 * DAY_MS).toISOString();
const since = new Date(now - 30 * DAY_MS).toISOString();
@@ -130,7 +126,6 @@ function createUserModules({ db, config, env = process.env, fetchImpl = globalTh
@@ -130,7 +126,6 @@ function createUserModules({ db, config, env = process.env, fetchImpl = globalTh
function start() {
function start() {
if (!enabled || timers.length) return false;
if (!enabled || timers.length) return false;
- ensureSchema();
const safe = (fn) => () => { fn().catch((err) => { stats.lastError = err.message; }); };
const safe = (fn) => () => { fn().catch((err) => { stats.lastError = err.message; }); };
timers = [setInterval(safe(async () => await scan()), 5 * 60 * 1000), setInterval(safe(async () => await refresh()), DAY_MS), setTimeout(safe(async () => await refresh()), 3 * 60 * 1000)];
timers = [setInterval(safe(async () => await scan()), 5 * 60 * 1000), setInterval(safe(async () => await refresh()), DAY_MS), setTimeout(safe(async () => await refresh()), 3 * 60 * 1000)];
for (const t of timers) t.unref?.();
for (const t of timers) t.unref?.();
@@ -138,7 +133,7 @@ function createUserModules({ db, config, env = process.env, fetchImpl = globalTh
@@ -138,7 +133,7 @@ function createUserModules({ db, config, env = process.env, fetchImpl = globalTh
}
}
function stop() { for (const t of timers) { clearInterval(t); clearTimeout(t); } timers = []; }
function stop() { for (const t of timers) { clearInterval(t); clearTimeout(t); } timers = []; }
- return { enabled, preferencesFor, ensureSchema, summarize, push, scan, refresh, start, stop, stats: () => ({ enabled, ...stats }) };
+ return { enabled, preferencesFor, summarize, push, scan, refresh, start, stop, stats: () => ({ enabled, ...stats }) };
}
}
/** The system-prompt lines a person's ai.preferences add ('' when none). */
/** The system-prompt lines a person's ai.preferences add ('' when none). */
@@ -42,14 +42,14 @@ function tmpDir() {
@@ -42,14 +42,14 @@ function tmpDir() {
}
}
/** Boot the service. `env` goes through config.load() and is also what secrets resolve against. */
/** Boot the service. `env` goes through config.load() and is also what secrets resolve against. */
-// A boot on a dir used before is a restart on the same database (as the SQLite file was): its database stays open.
+// A boot on a dir used before is a restart on the same database: its database stays open.
const restartable = new Map();
const restartable = new Map();
async function boot({ env = {}, dir, clock, fetchImpl, credentialFetch } = {}) {
async function boot({ env = {}, dir, clock, fetchImpl, credentialFetch } = {}) {
const reuse = dir !== undefined;
const reuse = dir !== undefined;
if (dir === undefined) dir = tmpDir();
if (dir === undefined) dir = tmpDir();
const fullEnv = {
const fullEnv = {
- NODE_ENV: 'test', PORT: '0', AI_DB_PATH: path.join(dir, 'ai.db'), OV_NETWORK_PUBLIC_KEY: publicKey,
+ NODE_ENV: 'test', PORT: '0', OV_NETWORK_PUBLIC_KEY: publicKey, AI_MEDIA_ANALYSIS_DIR: path.join(dir, 'media-tmp'),
AI_PROVIDER_RETRY_DELAY_MS: '5', AI_QUOTA_SERVICE_RPM: '0', AI_QUOTA_SERVICE_RPD: '0',
AI_PROVIDER_RETRY_DELAY_MS: '5', AI_QUOTA_SERVICE_RPM: '0', AI_QUOTA_SERVICE_RPD: '0',
...env,
...env,
};
};
@@ -1,7 +1,7 @@
@@ -1,7 +1,7 @@
#!/usr/bin/env node
#!/usr/bin/env node
/**
/**
* Runs every test in test/ — the files named *.test.js — each in its own process, and fails
* Runs every test in test/ — the files named *.test.js — each in its own process, and fails
- * if any of them fails. They use temp SQLite databases, a generated signing key, the deterministic
+ * if any of them fails. They use temp databases (PGlite by default, PostgreSQL with npm run test:pg), a generated signing key, the deterministic
* stub provider and local HTTP servers on random ports; none of them needs the network, a paid
* stub provider and local HTTP servers on random ports; none of them needs the network, a paid
* key or a running OpenVibe.Network.
* key or a running OpenVibe.Network.
*
*
@@ -75,7 +75,6 @@ t.test('runs for someone without preferences, or for nobody, are untouched', asy
@@ -75,7 +75,6 @@ t.test('runs for someone without preferences, or for nobody, are untouched', asy
t.test('ai.usage_summary: 30-day runs and tokens per person, written only when changed', async () => {
t.test('ai.usage_summary: 30-day runs and tokens per person, written only when changed', async () => {
const mods = require('../server/user-modules').createUserModules({ db: h.db, config: h.config || { networkInternalUrl: h.env.OV_NETWORK_INTERNAL_URL }, env: h.env, log: {} });
const mods = require('../server/user-modules').createUserModules({ db: h.db, config: h.config || { networkInternalUrl: h.env.OV_NETWORK_INTERNAL_URL }, env: h.env, log: {} });
- mods.ensureSchema();
const s = await mods.summarize(A.id);
const s = await mods.summarize(A.id);
assert.strictEqual(s.runs_30d, 2); assert.strictEqual(s.tokens_30d, 80);
assert.strictEqual(s.runs_30d, 2); assert.strictEqual(s.tokens_30d, 80);
assert.deepStrictEqual(s.by_service, { live: 2 });
assert.deepStrictEqual(s.by_service, { live: 2 });