Files
xinmi/scripts/verify_content_permission_migration.cjs
T

63 lines
5.2 KiB
JavaScript
Raw Normal View History

const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const crypto = require('node:crypto')
const { execFileSync } = require('node:child_process')
const schema = `content_permissions_${crypto.randomBytes(6).toString('hex')}`
const migration = fs.readFileSync(path.join(__dirname, '../migrations/versioned/000207_content_action_permissions.up.sql'), 'utf8')
const checks = []
const literal = value => `'${String(value).replaceAll("'", "''")}'`
function sql(query) {
return execFileSync('docker', ['--context', 'colima-yawan', 'exec', '-i', 'WeKnora-postgres',
'psql', '-X', '-q', '-U', 'postgres', '-d', 'yawan_native_acceptance_20260915', '-At', '-v', 'ON_ERROR_STOP=1'],
{ input: `SET search_path TO ${schema}, public;\n${query}`, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
}
function permissions(id) { return JSON.parse(sql(`SELECT permissions FROM workbench_access_roles WHERE id=${literal(id)}`)) }
function check(name, actual, expected) { assert.deepEqual(actual, expected, name); checks.push(name) }
function legacyRole(id, sourcePermissions, nativePermissions = [], version = 1) {
const spec = { id, source_role: id, name: id, source_permissions: sourcePermissions, permissions: nativePermissions, review_notes: [] }
return `INSERT INTO workbench_access_roles(id,tenant_id,name,permissions,version) VALUES(${literal(id)},10059,${literal(id)},${literal(JSON.stringify(nativePermissions))},${version});
INSERT INTO yawan_legacy_access_mappings(source_system,target_tenant_id,entity_key,source_spec,plan_sha256)
VALUES('yawan-workbench',10059,${literal('role/' + id)},${literal(JSON.stringify(spec))},'test');`
}
try {
sql(`CREATE SCHEMA ${schema};
CREATE TABLE ${schema}.workbench_access_roles (LIKE public.workbench_access_roles INCLUDING ALL);
CREATE TABLE ${schema}.yawan_legacy_access_mappings (LIKE public.yawan_legacy_access_mappings INCLUDING ALL);
CREATE TABLE ${schema}.workbench_content_permission_migrations(version integer PRIMARY KEY, changes jsonb NOT NULL, created_at timestamptz NOT NULL DEFAULT now());
INSERT INTO workbench_access_roles(id,tenant_id,name,permissions) VALUES
('producer',10059,'producer','["content.read","content.write"]'),
('reader',10059,'reader','["content.read"]'),
('other-module',10059,'other-module','["agents.read"]');
${legacyRole('reviewer', ['audit:read', 'read', 'review'])}
${legacyRole('viewer', ['read'])}
${legacyRole('edited-reviewer', ['review'], [], 2)}
${legacyRole('editor', ['tasks:review', 'tasks:write', 'tasks:deliver'], ['content.read', 'content.write'])}
${legacyRole('changed-reviewer', ['review'])}
UPDATE workbench_access_roles SET permissions='["agents.read"]' WHERE id='changed-reviewer';`)
const identityBefore = sql(`SELECT md5(coalesce(jsonb_agg(to_jsonb(u) ORDER BY id)::text,'')) FROM public.users u`)
const membershipBefore = sql(`SELECT md5(coalesce(jsonb_agg(to_jsonb(m) ORDER BY id)::text,'')) FROM public.tenant_members m`)
sql(`BEGIN; ${migration} COMMIT;`)
check('native_producer_preserves_review_and_delivery', permissions('producer'), ['content.deliver', 'content.read', 'content.review', 'content.write'])
check('native_reader_preserves_delivery_without_review', permissions('reader'), ['content.deliver', 'content.read'])
check('legacy_reviewer_restored_without_generation_or_delivery', permissions('reviewer'), ['content.read', 'content.review'])
check('generic_legacy_read_does_not_invent_task_access', permissions('viewer'), [])
check('edited_import_role_is_preserved', permissions('edited-reviewer'), [])
check('changed_import_permissions_are_preserved', permissions('changed-reviewer'), ['agents.read'])
check('legacy_editor_preserves_all_existing_content_actions', permissions('editor'), ['content.deliver', 'content.read', 'content.review', 'content.write'])
check('unrelated_module_unchanged', permissions('other-module'), ['agents.read'])
check('original_legacy_ledger_is_not_rewritten', JSON.parse(sql(`SELECT source_spec->'permissions' FROM yawan_legacy_access_mappings WHERE entity_key='role/reviewer'`)), [])
check('backfill_provenance_records_new_mapping', JSON.parse(sql(`SELECT c->'mapped_permissions' FROM workbench_content_permission_migrations,jsonb_array_elements(changes)c WHERE c->>'role_id'='reviewer'`)), ['content.read', 'content.review'])
sql(`UPDATE workbench_access_roles SET permissions='[]',version=version+1 WHERE id='reviewer';
INSERT INTO workbench_access_roles(id,tenant_id,name,permissions) VALUES('new-reader',10059,'new-reader','["content.read"]');
BEGIN; ${migration} COMMIT;`)
check('rerun_preserves_explicit_revocation', permissions('reviewer'), [])
check('rerun_does_not_expand_post_migration_roles', permissions('new-reader'), ['content.read'])
check('users_unchanged', sql(`SELECT md5(coalesce(jsonb_agg(to_jsonb(u) ORDER BY id)::text,'')) FROM public.users u`), identityBefore)
check('memberships_unchanged', sql(`SELECT md5(coalesce(jsonb_agg(to_jsonb(m) ORDER BY id)::text,'')) FROM public.tenant_members m`), membershipBefore)
console.log(JSON.stringify({ ok: true, checks }, null, 2))
} finally {
sql(`DROP SCHEMA IF EXISTS ${schema} CASCADE;`)
}