diff --git a/src/schemas.ts b/src/schemas.ts index bd704e4..18cadf3 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -16,6 +16,10 @@ import type { AdminPermission } from './types.js'; +// ADMIN_ROLES and ADMIN_PERMISSIONS must mirror the ratified vocabulary in +// @tummycrypt/tinyland-auth v0.4.0 (src/types/auth.ts ADMIN_ROLES, +// src/types/permissions.ts AdminPermission) exactly. See TIN-2435. +// tests/vocabulary-alignment.test.ts asserts this stays in sync. export const ADMIN_ROLES: AdminRole[] = [ 'super_admin', 'admin', @@ -23,6 +27,7 @@ export const ADMIN_ROLES: AdminRole[] = [ 'editor', 'event_manager', 'contributor', + 'member', 'viewer', ]; @@ -32,7 +37,6 @@ export const ADMIN_ROLES: AdminRole[] = [ export const ADMIN_PERMISSIONS: AdminPermission[] = [ 'admin.access', 'admin.users.manage', - 'admin.users.moderate', 'admin.content.manage', 'admin.content.moderate', 'admin.events.manage', @@ -63,6 +67,7 @@ export const adminUserSchema = z.object({ 'editor', 'event_manager', 'contributor', + 'member', 'viewer', ]), certificateCn: z.string().nullable().optional(), @@ -93,6 +98,7 @@ export const createAdminUserSchema = z.object({ 'editor', 'event_manager', 'contributor', + 'member', 'viewer', ]), totpEnabled: z.boolean().optional().default(false), @@ -114,6 +120,8 @@ export const updateAdminUserSchema = z.object({ 'editor', 'event_manager', 'moderator', + 'contributor', + 'member', 'viewer', ]) .optional(), diff --git a/src/types.ts b/src/types.ts index b89f97d..073581c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,10 +10,14 @@ +// Must mirror the ratified permission vocabulary in @tummycrypt/tinyland-auth +// v0.4.0 (src/types/permissions.ts AdminPermission). This is a validated +// subset (not the full 21-permission authority set); every entry here must +// also exist there. See TIN-2435; tests/vocabulary-alignment.test.ts asserts +// this stays in sync. export type AdminPermission = | 'admin.access' | 'admin.users.manage' - | 'admin.users.moderate' | 'admin.content.manage' | 'admin.content.moderate' | 'admin.events.manage' diff --git a/tests/admin-validation.test.ts b/tests/admin-validation.test.ts index 2680717..41aba53 100644 --- a/tests/admin-validation.test.ts +++ b/tests/admin-validation.test.ts @@ -162,11 +162,10 @@ describe('Types', () => { expect(log.admin_user_id).toBe('usr_1'); }); - it('AdminPermission type allows all 9 permission values', () => { + it('AdminPermission type allows all 8 permission values', () => { const perms: AdminPermission[] = [ 'admin.access', 'admin.users.manage', - 'admin.users.moderate', 'admin.content.manage', 'admin.content.moderate', 'admin.events.manage', @@ -174,7 +173,7 @@ describe('Types', () => { 'admin.settings.manage', 'admin.security.manage', ]; - expect(perms).toHaveLength(9); + expect(perms).toHaveLength(8); }); }); @@ -183,8 +182,8 @@ describe('Types', () => { describe('ADMIN_ROLES', () => { - it('has exactly 7 entries', () => { - expect(ADMIN_ROLES).toHaveLength(7); + it('has exactly 8 entries', () => { + expect(ADMIN_ROLES).toHaveLength(8); }); it('includes super_admin', () => { @@ -215,8 +214,8 @@ describe('ADMIN_ROLES', () => { expect(ADMIN_ROLES).toContain('viewer'); }); - it('does not include member', () => { - expect(ADMIN_ROLES).not.toContain('member'); + it('includes member', () => { + expect(ADMIN_ROLES).toContain('member'); }); it('is an array', () => { @@ -229,8 +228,8 @@ describe('ADMIN_ROLES', () => { describe('ADMIN_PERMISSIONS', () => { - it('has exactly 9 entries', () => { - expect(ADMIN_PERMISSIONS).toHaveLength(9); + it('has exactly 8 entries', () => { + expect(ADMIN_PERMISSIONS).toHaveLength(8); }); it('includes admin.access', () => { @@ -241,8 +240,8 @@ describe('ADMIN_PERMISSIONS', () => { expect(ADMIN_PERMISSIONS).toContain('admin.users.manage'); }); - it('includes admin.users.moderate', () => { - expect(ADMIN_PERMISSIONS).toContain('admin.users.moderate'); + it('does not include the dead admin.users.moderate string (TIN-2435)', () => { + expect(ADMIN_PERMISSIONS).not.toContain('admin.users.moderate'); }); it('includes admin.content.manage', () => { diff --git a/tests/vocabulary-alignment.test.ts b/tests/vocabulary-alignment.test.ts new file mode 100644 index 0000000..6f0c29c --- /dev/null +++ b/tests/vocabulary-alignment.test.ts @@ -0,0 +1,66 @@ +import { describe, it, expect } from 'vitest'; +import { ADMIN_ROLES, ADMIN_PERMISSIONS } from '../src/index.js'; + +// TIN-2435: ADMIN_ROLES and ADMIN_PERMISSIONS in this package must never +// drift from the ratified vocabulary in @tummycrypt/tinyland-auth v0.4.0. +// This package cannot import runtime values from @tummycrypt/tinyland-auth +// outside the Bazel module graph (it is only linked as a Bazel npm_link +// dependency, not an npm dependency of this package -- see MODULE.bazel / +// BUILD.bazel), so the authority's ADMIN_ROLES is transcribed here by hand +// from tinyland-auth src/types/auth.ts as of v0.4.0. If tinyland-auth's +// role vocabulary changes, this transcription -- and ADMIN_ROLES above -- +// must be updated together. +const AUTH_ADMIN_ROLES_V0_4_0 = [ + 'super_admin', + 'admin', + 'moderator', + 'editor', + 'event_manager', + 'contributor', + 'member', + 'viewer', +]; + +describe('vocabulary alignment with @tummycrypt/tinyland-auth v0.4.0 (TIN-2435)', () => { + it('ADMIN_ROLES matches the authority ADMIN_ROLES exactly (8 roles, includes member)', () => { + expect([...ADMIN_ROLES].sort()).toEqual([...AUTH_ADMIN_ROLES_V0_4_0].sort()); + expect(ADMIN_ROLES).toHaveLength(8); + expect(ADMIN_ROLES).toContain('member'); + }); + + it('ADMIN_PERMISSIONS contains no dead strings absent from the authority vocabulary', () => { + // The authority's 21-permission set (src/types/permissions.ts + // AdminPermission in tinyland-auth). This package validates a subset; + // every entry here must also be a member of the authority set. + const AUTHORITY_PERMISSIONS = [ + 'admin.access', + 'admin.users.view', + 'admin.users.manage', + 'admin.users.delete', + 'admin.content.view', + 'admin.content.publish', + 'admin.content.media_create', + 'admin.content.manage', + 'admin.content.moderate', + 'admin.content.delete', + 'admin.events.view', + 'admin.events.manage', + 'admin.events.delete', + 'admin.analytics.view', + 'admin.analytics.export', + 'admin.settings.view', + 'admin.settings.manage', + 'admin.security.view', + 'admin.security.manage', + 'admin.logs.view', + 'admin.logs.export', + ]; + + for (const perm of ADMIN_PERMISSIONS) { + expect(AUTHORITY_PERMISSIONS).toContain(perm); + } + + // The dead string dropped by TIN-2435 must not reappear. + expect(ADMIN_PERMISSIONS).not.toContain('admin.users.moderate'); + }); +});