1
0

refactor(audit): extract audit domain into self-contained module

Move the entire audit subsystem (system audit, API audit, import audit,
user lifecycle audit, frontend telemetry) from core into modules/audit/.

Core decoupling via interface-based injection:
- AuditRecorderInterface replaces SystemAuditService in 10+ core services
- UserLifecycleAuditInterface / ImportAuditInterface for specialized flows
- NullAuditRecorder fallback when audit module is disabled
- ApiBootstrap/ApiResponse use null-safe callable resolvers

Module structure (modules/audit/):
- Manifest with routes, permissions, scheduler jobs, authorization policy
- 9 services, 8 repositories, 6 domain enums, 4 job handlers
- 33 page files, 4 JS files, 8 test files, migration scripts, i18n

Core cleanup:
- OperationsAuthorizationPolicy, UiCapabilityMap, PermissionService
  surgically cleaned of audit-specific constants
- Sidebar template cleared of hardcoded audit navigation
- AuditModuleIsolationContractTest ensures no future core→module coupling

All quality gates pass: 1346 tests (19,276 assertions), PHPStan level 5
clean, architecture contracts verified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 21:12:49 +01:00
parent 12a837bda9
commit 0c351f6aff
176 changed files with 2157 additions and 834 deletions

View File

@@ -14,10 +14,6 @@ $canViewJobs = (bool) ($layoutAuth['can_view_jobs'] ?? false);
$canViewApiDocs = (bool) ($layoutAuth['can_view_api_docs'] ?? false);
$canViewDocs = (bool) ($layoutAuth['can_view_docs'] ?? false);
$canViewMailLog = (bool) ($layoutAuth['can_view_mail_log'] ?? false);
$canViewApiAudit = (bool) ($layoutAuth['can_view_api_audit'] ?? false);
$canViewSystemAudit = (bool) ($layoutAuth['can_view_system_audit'] ?? false);
$canViewImportsAudit = (bool) ($layoutAuth['can_view_imports_audit'] ?? false);
$canViewUserLifecycleAudit = (bool) ($layoutAuth['can_view_user_lifecycle_audit'] ?? false);
$canViewStats = (bool) ($layoutAuth['can_view_stats'] ?? false);
$canViewSystemInfo = (bool) ($layoutAuth['can_view_system_info'] ?? false);
$docsDefaultSlug = DocsCatalogService::defaultSlug();
@@ -26,7 +22,7 @@ $hasOrganization = $canViewTenants || $canViewDepartments || $canViewUsers;
$hasUsersSection = $canViewRoles || $canViewPermissions;
$hasAutomationSection = $canViewImports || $canViewJobs || $canViewApiDocs;
$hasMonitoringSection = $canViewStats;
$hasLogsSection = $canViewMailLog || $canViewApiAudit || $canViewSystemAudit || $canViewImportsAudit || $canViewUserLifecycleAudit;
$hasLogsSection = $canViewMailLog;
$hasSystemSection = $canViewSettings || $canViewSystemInfo || $canViewDocs;
$hasAdminPanel = layoutHasAdminPanel($layoutAuth);
@@ -140,34 +136,6 @@ $adminNavGroups = [
'visible' => $canViewMailLog,
'withTenant' => false,
],
[
'label' => t('API audit'),
'path' => 'admin/api-audit',
'active' => navActive('admin/api-audit', true),
'visible' => $canViewApiAudit,
'withTenant' => false,
],
[
'label' => t('System audit logs'),
'path' => 'admin/system-audit',
'active' => navActive('admin/system-audit', true),
'visible' => $canViewSystemAudit,
'withTenant' => false,
],
[
'label' => t('Import logs'),
'path' => 'admin/import-audit',
'active' => navActive('admin/import-audit', true),
'visible' => $canViewImportsAudit,
'withTenant' => false,
],
[
'label' => t('User lifecycle logs'),
'path' => 'admin/user-lifecycle-audit',
'active' => navActive('admin/user-lifecycle-audit', true),
'visible' => $canViewUserLifecycleAudit,
'withTenant' => false,
],
],
],
[
@@ -457,28 +425,6 @@ $moduleSearchSlots = is_array($moduleSlots['search.resource_item'] ?? null) ? $m
<ul class="app-search-preview" data-search-preview></ul>
</li>
<?php endif; ?>
<?php if ($canViewApiAudit): ?>
<?php $apiAuditSearch = navActive('admin/api-audit', true); ?>
<li data-search-key="api-audit" data-search-base="<?php e(lurl('admin/api-audit')); ?>">
<a href="<?php e(lurl('admin/api-audit')); ?>" class="<?php e($apiAuditSearch['class']); ?>"
<?php echo $apiAuditSearch['aria']; ?>>
<span><?php e(t('API audit')); ?></span>
<span class="badge" data-search-count>0</span>
</a>
<ul class="app-search-preview" data-search-preview></ul>
</li>
<?php endif; ?>
<?php if ($canViewSystemAudit): ?>
<?php $systemAuditSearch = navActive('admin/system-audit', true); ?>
<li data-search-key="system-audit" data-search-base="<?php e(lurl('admin/system-audit')); ?>">
<a href="<?php e(lurl('admin/system-audit')); ?>" class="<?php e($systemAuditSearch['class']); ?>"
<?php echo $systemAuditSearch['aria']; ?>>
<span><?php e(t('System audit')); ?></span>
<span class="badge" data-search-count>0</span>
</a>
<ul class="app-search-preview" data-search-preview></ul>
</li>
<?php endif; ?>
<?php
$coreSearchKeys = [
'users' => true,
@@ -491,8 +437,6 @@ $moduleSearchSlots = is_array($moduleSlots['search.resource_item'] ?? null) ? $m
'docs' => true,
'hotkeys' => true,
'mail-log' => true,
'api-audit' => true,
'system-audit' => true,
];
foreach ($moduleSearchSlots as $searchSlot):
if (!is_array($searchSlot)) { continue; }