Files
stack/packages/bus/tests/schema-3w.test.mjs
jason.woltje e6ab8bc1fd feat(bus,plugins,organization,cli,webui): SP9 remove system types outright, bus schema 3v-3w drops project_system_types (#1873)
Jason's 2026-11-12 ruling: the only deployed system is the dev environment on
dragon-lin, so the system-type plug-in kind goes with no retired kind, fence,
conversion or new refusal code.

- plugins: system-type leaves KINDS; system-types.mjs and the five modules are
  deleted. An org file naming the kind refuses unknown-kind.
- organization: the plugins.system-type check, systemTypesFence and the
  system-types.console right are removed.
- bus: system-types.console, project.system-types[.set], the system-type half
  of the tester check, the runtime reads and system-type-not-enabled are
  removed. Step 3v-3w drops project_system_types, its index, its four guards
  and its rows; SCHEMA_VERSION is 3w (32 tables, 126 triggers, 5 views).
- cli: mosaic org system-types and mosaic project system-types[-set] are
  removed; stack-release COUNTED and the budget supervisor's closed list move
  to 3w.
- channel: the project.system-types verbs are removed.
- webui: the System types page, route, dashboard row, chips and project
  selection are removed.
- docs: REQ-SYS-1/2 now describe starter packs; README system-type text and
  the requirements titles follow.
2026-11-12 21:02:46 -06:00

74 lines
4.1 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { DatabaseSync } from 'node:sqlite';
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { SCHEMA_VERSION, Store, schemaDigest } from '../src/store.mjs';
// #1873 SP9: bus schema step 3v-3w drops project_system_types with its index, its four triggers and
// its rows (Jason's 2026-11-12 ruling removes the system-type kind outright).
const TABLE = 'project_system_types';
const ddl = readFileSync(new URL('../schema.sql', import.meta.url), 'utf8');
const createdBy3u = readFileSync(new URL('../src/migrations/3t-3u.sql', import.meta.url), 'utf8');
const named = (path) => {
const db = new DatabaseSync(path, { readOnly: true });
try {
return {
master: db.prepare('SELECT type,name FROM sqlite_master WHERE name LIKE ? OR tbl_name=? ORDER BY type,name').all(`${TABLE}%`, TABLE).map((r) => ({ ...r })),
sequence: db.prepare('SELECT name FROM sqlite_sequence WHERE name=?').all(TABLE).map((r) => r.name),
digest: schemaDigest(db),
version: db.prepare("SELECT value FROM meta WHERE key='schema_version'").get().value,
};
} finally {
db.close();
}
};
test('SP9 a store opened at schema 3w has no project_system_types table', (t) => {
assert.equal(SCHEMA_VERSION, '3w');
assert.equal(ddl.includes(TABLE), false, 'schema.sql no longer creates the table');
const root = mkdtempSync(join(tmpdir(), 'sp9-fresh-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
new Store(root).close();
const fresh = named(join(root, 'bus/bus.sqlite'));
assert.equal(fresh.version, '3w');
assert.deepEqual(fresh.master, []);
});
test('SP9 a 3v store holding project_system_types rows migrates to 3w with nothing of the table left', (t) => {
const root = mkdtempSync(join(tmpdir(), 'sp9-3v-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
mkdirSync(join(root, 'bus'), { mode: 0o700 });
const path = join(root, 'bus/bus.sqlite');
const db = new DatabaseSync(path);
// The 3v schema is today's schema.sql plus what 3t-3u created; the store's step check refuses any
// other shape, so a migration that succeeds proves the fixture is a real 3v store.
db.exec(ddl + createdBy3u);
db.prepare('INSERT INTO meta VALUES (?,?)').run('schema_version', '3v');
db.prepare('INSERT INTO meta VALUES (?,?)').run('schema_digest', schemaDigest(db));
db.prepare('INSERT INTO meta VALUES (?,?)').run('comment_cursor_epoch', '2026-11-12T00:00:00.000Z');
db.prepare('INSERT INTO projects(id,at,business,name,repo,created_by) VALUES(?,?,?,?,?,?)').run('p1', '2026-11-12T00:00:00.000Z', 'acme', 'example', null, 'jason');
const select = db.prepare(`INSERT INTO ${TABLE}(at,business,project,types,by) VALUES(?,?,?,?,?)`);
select.run('2026-11-12T00:00:01.000Z', 'acme', 'p1', '["coding"]', 'jason');
select.run('2026-11-12T00:00:02.000Z', 'acme', 'p1', '["coding","montage"]', 'jason');
db.close();
chmodSync(path, 0o600);
const before = named(path);
assert.deepEqual(before.master.map((r) => r.name).sort(), [TABLE, `${TABLE}_business`, `${TABLE}_no_delete`, `${TABLE}_no_replace`, `${TABLE}_no_update`, `${TABLE}_project`]);
assert.deepEqual(before.sequence, [TABLE]);
assert.throws(() => new Store(root), /schema-mismatch/);
assert.deepEqual(named(path), before, 'the refused open changed nothing');
const store = new Store(root, { migrateSchema: true });
t.after(() => store.close());
assert.equal(store.get('SELECT name FROM projects WHERE id=?', 'p1').name, 'example', 'other rows stay');
const after = named(path);
assert.equal(after.version, '3w');
assert.deepEqual(after.master, [], 'no table, index or trigger of that name is left');
assert.deepEqual(after.sequence, [], 'no sqlite_sequence row of that name is left');
const freshRoot = mkdtempSync(join(tmpdir(), 'sp9-ref-'));
t.after(() => rmSync(freshRoot, { recursive: true, force: true }));
new Store(freshRoot).close();
assert.equal(after.digest, named(join(freshRoot, 'bus/bus.sqlite')).digest, 'the migrated schema is the fresh one');
});