openvibers/openvibe.live Commits
Expand/migrate/contract check (WS-P task 12): a new DROP TABLE, DROP COLUMN or rename of a real table in server/ fails the suite until test/fixtures/destructive-migrations.json lists it with its dated contract step; the five known table rebuilds are recorded
Changes
nnew file mode 100644@@@ -0,0 +1,45 @@++'use strict';++/**++ * Expand/migrate/contract (roadmap WS-P task 12; ADR-028 in OpenVibe.Contracts): a schema change that++ * removes or renames something (DROP TABLE, DROP COLUMN, RENAME COLUMN, RENAME TO) breaks the release++ * production may roll back to, so it is only allowed as a contract step: listed in++ * test/fixtures/destructive-migrations.json with the dated plan entry that runs it after a release where++ * nothing reads the old shape. Everything else is additive (CREATE … IF NOT EXISTS, ADD COLUMN, new++ * tables) and test/rollback-newer-writes.test.js proves the older release still works on it.++ */++const assert = require('assert');++const fs = require('fs');++const path = require('path');++++const ROOT = path.join(__dirname, '..');++const RE = /\b(DROP\s+TABLE(?:\s+IF\s+EXISTS)?\s+[`"]?([A-Za-z_][A-Za-z0-9_]*)|DROP\s+COLUMN\s+[`"]?([A-Za-z_][A-Za-z0-9_]*)|RENAME\s+COLUMN\s+[`"]?([A-Za-z_][A-Za-z0-9_]*)|RENAME\s+TO\s+[`"]?([A-Za-z_][A-Za-z0-9_]*))/gi;++const TEMP = /^_|_new$|_old$|_tmp$|^tmp_/i;++++const found = new Map(); // "file|STATEMENT" -> count++(function walk(dir) {++ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {++ const p = path.join(dir, e.name);++ if (e.isDirectory()) { if (e.name !== 'node_modules') walk(p); continue; }++ if (!p.endsWith('.js')) continue;++ const src = fs.readFileSync(p, 'utf8');++ let m;++ while ((m = RE.exec(src))) {++ const name = m[2] || m[3] || m[4] || m[5];++ if (TEMP.test(name) || /\$\{/.test(name)) continue;++ const statement = m[1].replace(/[`"]/g, '').replace(/\s+/g, ' ').toUpperCase();++ const key = `${path.relative(ROOT, p)}|${statement}`;++ found.set(key, (found.get(key) || 0) + 1);++ }++ }++})(path.join(ROOT, 'server'));++++const allow = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures', 'destructive-migrations.json'), 'utf8')).allowed;++const allowed = new Map(allow.map((a) => [`${a.file}|${a.statement}`, a]));++const unlisted = [];++for (const [key, n] of found) {++ const a = allowed.get(key);++ if (!a || n > a.count) unlisted.push(`${key.replace('|', ': ')} ×${n}`);++}++assert.deepStrictEqual(unlisted, [], 'a destructive schema change needs its contract step in test/fixtures/destructive-migrations.json');++for (const a of allow) assert.ok(a.contract && a.contract.length > 10, `${a.statement}: say which contract step runs it`);++console.log(`destructive migrations: ${found.size} known statement(s), each with its contract step; nothing new`);nnew file mode 100644@@@ -0,0 +1,15 @@++{++ "_about": "Destructive schema statements in server/ that are known and accounted for (roadmap WS-P task 12, expand/migrate/contract). A new DROP TABLE, DROP COLUMN, RENAME COLUMN or RENAME TO of a real table fails test/destructive-migrations.test.js until it is listed here with its contract step: the dated plan entry (section 10) that runs it after a release where nothing reads the old shape, with a backup. Temporary tables (_x, x_new, x_old, x_tmp) are not counted.",++ "allowed": [++ { "file": "server/db/database.js", "statement": "DROP TABLE CHAT_MESSAGES", "count": 2, "contract": "pre-ADR table rebuild (copy every column into chat_messages_new, drop, rename); guarded, one-time" },++ { "file": "server/db/database.js", "statement": "RENAME TO CHAT_MESSAGES", "count": 2, "contract": "the same rebuild" },++ { "file": "server/db/database.js", "statement": "DROP TABLE ROBOTSTREAMER_INTEGRATIONS", "count": 1, "contract": "pre-ADR table rebuild keeping every row; guarded, one-time" },++ { "file": "server/db/database.js", "statement": "RENAME TO ROBOTSTREAMER_INTEGRATIONS", "count": 1, "contract": "the same rebuild" },++ { "file": "server/db/database.js", "statement": "DROP TABLE EMOTES", "count": 1, "contract": "pre-ADR table rebuild keeping every row; guarded, one-time" },++ { "file": "server/db/database.js", "statement": "RENAME TO EMOTES", "count": 1, "contract": "the same rebuild" },++ { "file": "server/db/database.js", "statement": "DROP TABLE CHANNEL_SOUNDS", "count": 1, "contract": "pre-ADR table rebuild keeping every row; guarded, one-time" },++ { "file": "server/db/database.js", "statement": "RENAME TO CHANNEL_SOUNDS", "count": 1, "contract": "the same rebuild" },++ { "file": "server/db/database.js", "statement": "DROP TABLE STREAM_CONTROLS", "count": 1, "contract": "pre-ADR table rebuild keeping every row; guarded, one-time" },++ { "file": "server/db/database.js", "statement": "RENAME TO STREAM_CONTROLS", "count": 1, "contract": "the same rebuild" }++ ]++}
new file mode 100644
new file mode 100644
@@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
+'use strict';
+/**
+ * Expand/migrate/contract (roadmap WS-P task 12; ADR-028 in OpenVibe.Contracts): a schema change that
+ * removes or renames something (DROP TABLE, DROP COLUMN, RENAME COLUMN, RENAME TO) breaks the release
+ * production may roll back to, so it is only allowed as a contract step: listed in
+ * test/fixtures/destructive-migrations.json with the dated plan entry that runs it after a release where
+ * nothing reads the old shape. Everything else is additive (CREATE … IF NOT EXISTS, ADD COLUMN, new
+ * tables) and test/rollback-newer-writes.test.js proves the older release still works on it.
+ */
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.join(__dirname, '..');
+const RE = /\b(DROP\s+TABLE(?:\s+IF\s+EXISTS)?\s+[`"]?([A-Za-z_][A-Za-z0-9_]*)|DROP\s+COLUMN\s+[`"]?([A-Za-z_][A-Za-z0-9_]*)|RENAME\s+COLUMN\s+[`"]?([A-Za-z_][A-Za-z0-9_]*)|RENAME\s+TO\s+[`"]?([A-Za-z_][A-Za-z0-9_]*))/gi;
+const TEMP = /^_|_new$|_old$|_tmp$|^tmp_/i;
+
+const found = new Map(); // "file|STATEMENT" -> count
+(function walk(dir) {
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
+ const p = path.join(dir, e.name);
+ if (e.isDirectory()) { if (e.name !== 'node_modules') walk(p); continue; }
+ if (!p.endsWith('.js')) continue;
+ const src = fs.readFileSync(p, 'utf8');
+ let m;
+ while ((m = RE.exec(src))) {
+ const name = m[2] || m[3] || m[4] || m[5];
+ if (TEMP.test(name) || /\$\{/.test(name)) continue;
+ const statement = m[1].replace(/[`"]/g, '').replace(/\s+/g, ' ').toUpperCase();
+ const key = `${path.relative(ROOT, p)}|${statement}`;
+ found.set(key, (found.get(key) || 0) + 1);
+ }
+ }
+})(path.join(ROOT, 'server'));
+
+const allow = JSON.parse(fs.readFileSync(path.join(__dirname, 'fixtures', 'destructive-migrations.json'), 'utf8')).allowed;
+const allowed = new Map(allow.map((a) => [`${a.file}|${a.statement}`, a]));
+const unlisted = [];
+for (const [key, n] of found) {
+ const a = allowed.get(key);
+ if (!a || n > a.count) unlisted.push(`${key.replace('|', ': ')} ×${n}`);
+}
+assert.deepStrictEqual(unlisted, [], 'a destructive schema change needs its contract step in test/fixtures/destructive-migrations.json');
+for (const a of allow) assert.ok(a.contract && a.contract.length > 10, `${a.statement}: say which contract step runs it`);
+console.log(`destructive migrations: ${found.size} known statement(s), each with its contract step; nothing new`);
new file mode 100644
new file mode 100644
@@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
+{
+ "_about": "Destructive schema statements in server/ that are known and accounted for (roadmap WS-P task 12, expand/migrate/contract). A new DROP TABLE, DROP COLUMN, RENAME COLUMN or RENAME TO of a real table fails test/destructive-migrations.test.js until it is listed here with its contract step: the dated plan entry (section 10) that runs it after a release where nothing reads the old shape, with a backup. Temporary tables (_x, x_new, x_old, x_tmp) are not counted.",
+ "allowed": [
+ { "file": "server/db/database.js", "statement": "DROP TABLE CHAT_MESSAGES", "count": 2, "contract": "pre-ADR table rebuild (copy every column into chat_messages_new, drop, rename); guarded, one-time" },
+ { "file": "server/db/database.js", "statement": "RENAME TO CHAT_MESSAGES", "count": 2, "contract": "the same rebuild" },
+ { "file": "server/db/database.js", "statement": "DROP TABLE ROBOTSTREAMER_INTEGRATIONS", "count": 1, "contract": "pre-ADR table rebuild keeping every row; guarded, one-time" },
+ { "file": "server/db/database.js", "statement": "RENAME TO ROBOTSTREAMER_INTEGRATIONS", "count": 1, "contract": "the same rebuild" },
+ { "file": "server/db/database.js", "statement": "DROP TABLE EMOTES", "count": 1, "contract": "pre-ADR table rebuild keeping every row; guarded, one-time" },
+ { "file": "server/db/database.js", "statement": "RENAME TO EMOTES", "count": 1, "contract": "the same rebuild" },
+ { "file": "server/db/database.js", "statement": "DROP TABLE CHANNEL_SOUNDS", "count": 1, "contract": "pre-ADR table rebuild keeping every row; guarded, one-time" },
+ { "file": "server/db/database.js", "statement": "RENAME TO CHANNEL_SOUNDS", "count": 1, "contract": "the same rebuild" },
+ { "file": "server/db/database.js", "statement": "DROP TABLE STREAM_CONTROLS", "count": 1, "contract": "pre-ADR table rebuild keeping every row; guarded, one-time" },
+ { "file": "server/db/database.js", "statement": "RENAME TO STREAM_CONTROLS", "count": 1, "contract": "the same rebuild" }
+ ]
+}