a tested migration script for the capabilities→permissions rename
Other machines have to make the same database change, and pasted SQL is the
wrong way to ship it. `scripts/rename-capabilities-to-permissions.ts` renames
the table, its column, both indexes and its three CHECK constraints in one
transaction, and refuses to guess:
neither table nothing to do; db:push will create it
already renamed no-op, prints the grant count
BOTH tables present stops and says a human must decide
old table only migrates, counts before and after, prints every grant
Testing it found a bug that reading it had not. The existence checks were
inside the transaction but ran on the pool rather than on `tx`, so they could
not see the uncommitted rename: `columnExists('role_permissions','capability')`
answered false because that table did not exist yet on that connection, and the
COLUMN rename was silently skipped. The result was a `role_permissions` table
with a `capability` column — half migrated, and only failing on the next query.
That was found by building a scratch database in edge-pertento's exact shape
and running the script against it, rather than by review. Every check now
happens before the transaction, against the old names.
Verified end to end on that scratch: 6 grants migrated intact, a second run
correctly no-ops, and `db:push` afterwards leaves role_permissions and its rows
alone while creating the rest of the schema. Indexes come out as
role_permissions_pkey and uq_role_permissions_role_permission.
Also swept the last all-caps survivors the case-sensitive passes missed:
CORE_CAPABILITIES → CORE_PERMISSIONS, and a react-query key still spelling
['ROLE_CAPABILITIES']. The only CAPABILITIES left in src/ is the wallet's, which
is Lightning and stays.
This commit is contained in:
+1
-1
@@ -34,7 +34,7 @@ type PermissionsResponse = {
|
||||
|
||||
type Level = 'none' | 'read' | 'write';
|
||||
|
||||
const PERMISSIONS_KEY = ['ROLE_CAPABILITIES'];
|
||||
const PERMISSIONS_KEY = ['ROLE_PERMISSIONS'];
|
||||
|
||||
export const PermissionsSection = () => {
|
||||
const client = useClient();
|
||||
|
||||
@@ -2,7 +2,7 @@ import { getUserById, getRoleGrants } from 'officerdb';
|
||||
import type { UserRole } from 'officerdb';
|
||||
import {
|
||||
PERMISSION_BY_KEY,
|
||||
CORE_CAPABILITIES,
|
||||
CORE_PERMISSIONS,
|
||||
permissionForApiPath,
|
||||
permissionForWsProvider,
|
||||
isRequestAllowedAtLevel,
|
||||
@@ -74,7 +74,7 @@ export async function getEffectivePermissions(userId: number | undefined): Promi
|
||||
if (user.role === 'Super Admin') return { isOwner: true, grants: new Map() };
|
||||
|
||||
const grants = new Map<string, PermissionLevel>();
|
||||
for (const permission of CORE_CAPABILITIES) grants.set(permission.key, 'write');
|
||||
for (const permission of CORE_PERMISSIONS) grants.set(permission.key, 'write');
|
||||
|
||||
// Whether the kernel can enforce a boundary for this account. `confined` permissions are dropped
|
||||
// without it — see below.
|
||||
|
||||
@@ -461,7 +461,7 @@ export const DEFAULT_ROLE_PERMISSIONS: string[] = CORE_REGISTRY.filter((c) => c.
|
||||
* From CORE_REGISTRY, so a plugin declaring `kind: 'core'` — which its manifest cannot express, but which
|
||||
* a future bug could smuggle in — still could not grant itself to everyone undeniably.
|
||||
*/
|
||||
export const CORE_CAPABILITIES = CORE_REGISTRY.filter((c) => c.kind === 'core');
|
||||
export const CORE_PERMISSIONS = CORE_REGISTRY.filter((c) => c.kind === 'core');
|
||||
|
||||
/**
|
||||
* Replace the plugin half of the registry. Called after every install, uninstall, enable and disable.
|
||||
|
||||
Reference in New Issue
Block a user