fix(clients): archiving is edit-level, not delete (unblocks Sales/Director)
All checks were successful
Build & Push Docker Images / lint (push) Successful in 3m5s
Build & Push Docker Images / build-and-push (push) Successful in 8m38s

Every client-archive route gated on clients:delete, which only super_admin
holds, so Sales Agent/Manager and Director users (clients.delete=false) got
403 on archive — the button was shown but never worked. Archiving is
reversible, so gate it on clients:edit instead; permanent deletion stays the
separate admin:permanently_delete_clients permission.

- New single source of truth CLIENT_ARCHIVE_ACTION='edit' (permissions.ts).
- All 5 archive routes use it: [id] DELETE, archive, archive-dossier,
  bulk-archive-preflight, bulk POST.
- UI affordances gate on clients:edit (detail-header archive/restore, bulk
  archive, per-row list + mobile-card archive) so view-only users don't see a
  button that 403s.
- permission-matrix regression test locks the policy (Sales/Director/Agent can
  archive; Viewer cannot).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2qc3xZTfif7N4Wq3QDa8X
This commit is contained in:
2026-06-28 18:16:09 +02:00
parent 9f5810e3df
commit 32b57354ad
11 changed files with 118 additions and 32 deletions

View File

@@ -15,6 +15,7 @@
import { describe, it, expect, vi } from 'vitest';
import { withPermission, deepMerge, type AuthContext } from '@/lib/api/helpers';
import { CLIENT_ARCHIVE_ACTION } from '@/lib/auth/permissions';
import {
makeViewerPermissions,
makeSalesAgentPermissions,
@@ -64,6 +65,43 @@ async function checkPermission(
return response.status;
}
// ─── client archive policy ─────────────────────────────────────────────────────
/**
* Archiving a client is REVERSIBLE (sets archivedAt; restorable), so it is an
* edit-level action — NOT the destructive `clients:delete` (which only
* super_admin holds) and NOT `admin:permanently_delete_clients` (hard delete).
* Regression guard: prod showed Sales Managers/Directors getting 403 on archive
* because every archive route gated on `delete`. All archive routes now gate on
* CLIENT_ARCHIVE_ACTION; if anyone flips it back to a permission the sales roles
* lack, these fail.
*/
describe('Permission Matrix - client archive policy', () => {
it('archiving is an edit-level action, not delete', () => {
expect(CLIENT_ARCHIVE_ACTION).toBe('edit');
});
it('a Sales Manager can archive a client', async () => {
const ctx = makeCtx({ permissions: makeSalesManagerPermissions() });
expect(await checkPermission(ctx, 'clients', CLIENT_ARCHIVE_ACTION)).toBe(200);
});
it('a Director can archive a client', async () => {
const ctx = makeCtx({ permissions: makeDirectorPermissions() });
expect(await checkPermission(ctx, 'clients', CLIENT_ARCHIVE_ACTION)).toBe(200);
});
it('a Sales Agent can archive a client', async () => {
const ctx = makeCtx({ permissions: makeSalesAgentPermissions() });
expect(await checkPermission(ctx, 'clients', CLIENT_ARCHIVE_ACTION)).toBe(200);
});
it('a Viewer cannot archive a client', async () => {
const ctx = makeCtx({ permissions: makeViewerPermissions() });
expect(await checkPermission(ctx, 'clients', CLIENT_ARCHIVE_ACTION)).toBe(403);
});
});
// ─── super_admin ──────────────────────────────────────────────────────────────
describe('Permission Matrix - super_admin', () => {