Implemented the final operator workflow for the Governance Inbox. This includes refactoring the inbox page, updating finding resources, adding UI enforcement policies, updating related blade views, and adding comprehensive tests for operator workflow and scope contracts. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #418
415 lines
16 KiB
PHP
415 lines
16 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Governance\GovernanceInbox;
|
|
use App\Models\AlertDelivery;
|
|
use App\Models\Finding;
|
|
use App\Models\FindingException;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\ManagedEnvironmentTriageReview;
|
|
use App\Models\OperationRun;
|
|
use App\Support\BackupHealth\TenantBackupHealthResolver;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\PortfolioTriage\ManagedEnvironmentTriageReviewFingerprint;
|
|
use App\Support\PortfolioTriage\PortfolioArrivalContextToken;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
|
|
it('documents the Spec 327 governance inbox repo truth map', function (): void {
|
|
$path = repo_path('specs/327-governance-inbox-decision-first-workbench-productization/repo-truth-map.md');
|
|
|
|
expect($path)->toBeFile();
|
|
|
|
$contents = (string) file_get_contents($path);
|
|
|
|
expect($contents)
|
|
->toContain('Findings')
|
|
->toContain('Finding Exceptions / Accepted Risks')
|
|
->toContain('OperationRun links')
|
|
->toContain('Workspace / Environment filter state')
|
|
->toContain('Diagnostics');
|
|
});
|
|
|
|
it('renders the Spec 327 decision-first workbench for the highest-priority finding', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create([
|
|
'status' => 'active',
|
|
'name' => 'Spec327 Environment Alpha',
|
|
]);
|
|
[$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'owner');
|
|
|
|
Finding::factory()
|
|
->for($tenant)
|
|
->assignedTo((int) $user->getKey())
|
|
->ownedBy((int) $user->getKey())
|
|
->overdueByHours()
|
|
->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'subject_external_id' => 'spec327-priority-finding',
|
|
'severity' => Finding::SEVERITY_HIGH,
|
|
'status' => Finding::STATUS_IN_PROGRESS,
|
|
'evidence_jsonb' => [
|
|
'summary' => [
|
|
'kind' => 'policy_snapshot',
|
|
'raw_payload' => 'raw payload should stay hidden',
|
|
'debug_metadata' => 'debug metadata should stay hidden',
|
|
],
|
|
],
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
|
|
->get(GovernanceInbox::getUrl(panel: 'admin'))
|
|
->assertOk()
|
|
->assertSee('Governance Inbox')
|
|
->assertSee('Daily operator queue for governance follow-up, accepted risk, evidence gaps, and review handoff.')
|
|
->assertSee('Open governance work')
|
|
->assertSee('Primary inbox lanes')
|
|
->assertSee('Reason')
|
|
->assertSee('Impact')
|
|
->assertSee('Environment')
|
|
->assertSee('Review finding')
|
|
->assertSee('Evidence captured on finding')
|
|
->assertSee('No accepted risk')
|
|
->assertSee('More context')
|
|
->assertSee('Owner / due')
|
|
->assertSee('Next recommended action')
|
|
->assertSee('Linked records')
|
|
->assertSee('Source detail')
|
|
->assertDontSee('No governance items need attention.')
|
|
->assertDontSee('tenant filter')
|
|
->assertDontSee('current tenant')
|
|
->assertDontSee('entitled tenant')
|
|
->assertDontSee('all tenants')
|
|
->assertDontSee('raw payload should stay hidden')
|
|
->assertDontSee('debug metadata should stay hidden');
|
|
});
|
|
|
|
it('renders a compact empty decision state without primary zero metric cards', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create([
|
|
'status' => 'active',
|
|
'name' => 'Spec327 Empty Environment',
|
|
]);
|
|
[$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'owner');
|
|
|
|
$response = $this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
|
|
->get(GovernanceInbox::getUrl(panel: 'admin'));
|
|
|
|
$response
|
|
->assertOk()
|
|
->assertSee('Open governance work')
|
|
->assertSee('No governance items need attention.')
|
|
->assertSee('Findings, decisions, accepted-risk reviews, evidence gaps, and review follow-ups will appear here when they need operator attention.')
|
|
->assertSee('Diagnostics / source detail')
|
|
->assertSee('Collapsed')
|
|
->assertDontSee('Decision workbench')
|
|
->assertDontSee('raw payload')
|
|
->assertDontSee('stack trace')
|
|
->assertDontSee('debug metadata')
|
|
->assertDontSee('provider secret');
|
|
});
|
|
|
|
it('renders honest missing owner due evidence and accepted-risk states', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create([
|
|
'status' => 'active',
|
|
'name' => 'Spec327 Environment Missing State',
|
|
]);
|
|
[$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'owner');
|
|
|
|
Finding::factory()
|
|
->for($tenant)
|
|
->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'subject_external_id' => 'spec327-missing-state-finding',
|
|
'owner_user_id' => null,
|
|
'assignee_user_id' => null,
|
|
'due_at' => null,
|
|
'evidence_jsonb' => [],
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
|
|
->get(GovernanceInbox::getUrl(panel: 'admin'))
|
|
->assertOk()
|
|
->assertSee('Owner missing')
|
|
->assertSee('Due date unavailable')
|
|
->assertSee('Evidence missing')
|
|
->assertSee('No accepted risk')
|
|
->assertSee('Triage finding');
|
|
});
|
|
|
|
it('renders accepted-risk and exception state without exposing raw diagnostics by default', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create([
|
|
'status' => 'active',
|
|
'name' => 'Spec327 Environment Exception',
|
|
]);
|
|
[$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'owner');
|
|
|
|
$finding = Finding::factory()
|
|
->for($tenant)
|
|
->riskAccepted()
|
|
->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'subject_external_id' => 'spec327-exception-finding',
|
|
]);
|
|
|
|
FindingException::query()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'finding_id' => (int) $finding->getKey(),
|
|
'requested_by_user_id' => (int) $user->getKey(),
|
|
'owner_user_id' => null,
|
|
'status' => FindingException::STATUS_PENDING,
|
|
'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT,
|
|
'request_reason' => 'Exception needs governance evidence',
|
|
'requested_at' => now()->subDay(),
|
|
'review_due_at' => now()->addDay(),
|
|
'evidence_summary' => ['reference_count' => 0],
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
|
|
->get(GovernanceInbox::getUrl(panel: 'admin').'?family=finding_exceptions')
|
|
->assertOk()
|
|
->assertSee('Risk / exception review')
|
|
->assertSee('Exception needs governance evidence')
|
|
->assertSee('Pending exception')
|
|
->assertSee('Evidence missing')
|
|
->assertSee('Review accepted risk')
|
|
->assertSee('Diagnostics / source detail')
|
|
->assertSee('Collapsed')
|
|
->assertDontSee('raw payload')
|
|
->assertDontSee('stack trace')
|
|
->assertDontSee('provider secret')
|
|
->assertDontSee('internal exception')
|
|
->assertDontSee('debug metadata');
|
|
});
|
|
|
|
it('keeps source-family filter navigation anchored and expanded on filtered inbox URLs', function (): void {
|
|
[$user, $tenant] = createUserWithTenant(role: 'owner');
|
|
|
|
Finding::factory()
|
|
->for($tenant)
|
|
->assignedTo((int) $user->getKey())
|
|
->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'subject_external_id' => 'anchored-source-family-finding',
|
|
]);
|
|
|
|
$response = $this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
|
|
->get(GovernanceInbox::getUrl(panel: 'admin').'?family=assigned_findings')
|
|
->assertOk()
|
|
->assertSee('Assigned findings')
|
|
->assertSee('Open my findings');
|
|
|
|
$content = $response->getContent();
|
|
|
|
expect($content)
|
|
->toContain('id="source-detail"')
|
|
->toContain('family=assigned_findings#source-detail')
|
|
->toContain('family=finding_exceptions#source-detail');
|
|
|
|
expect((bool) preg_match('/<details[^>]*id="source-detail"[^>]*open|<details[^>]*open[^>]*id="source-detail"/s', $content))
|
|
->toBeTrue();
|
|
|
|
expect((bool) preg_match('/<a[^>]*family=assigned_findings#source-detail[^>]*aria-current="page"|<a[^>]*aria-current="page"[^>]*family=assigned_findings#source-detail/s', $content))
|
|
->toBeTrue();
|
|
});
|
|
|
|
it('renders visible governance attention sections on the governance inbox page', function (): void {
|
|
$alphaTenant = ManagedEnvironment::factory()->create([
|
|
'status' => 'active',
|
|
'name' => 'Alpha ManagedEnvironment',
|
|
'external_id' => 'alpha-tenant',
|
|
]);
|
|
[$user, $alphaTenant] = createUserWithTenant($alphaTenant, role: 'owner', workspaceRole: 'owner');
|
|
|
|
$bravoTenant = ManagedEnvironment::factory()->create([
|
|
'status' => 'active',
|
|
'workspace_id' => (int) $alphaTenant->workspace_id,
|
|
'name' => 'Bravo ManagedEnvironment',
|
|
'external_id' => 'bravo-tenant',
|
|
]);
|
|
|
|
$user->tenants()->syncWithoutDetaching([
|
|
(int) $bravoTenant->getKey() => ['role' => 'owner'],
|
|
]);
|
|
|
|
Finding::factory()
|
|
->for($alphaTenant)
|
|
->assignedTo((int) $user->getKey())
|
|
->ownedBy((int) $user->getKey())
|
|
->overdueByHours()
|
|
->create();
|
|
|
|
Finding::factory()
|
|
->for($bravoTenant)
|
|
->reopened()
|
|
->create();
|
|
|
|
$exceptionFinding = Finding::factory()
|
|
->for($alphaTenant)
|
|
->riskAccepted()
|
|
->create([
|
|
'workspace_id' => (int) $alphaTenant->workspace_id,
|
|
'subject_external_id' => 'exception-governance-home',
|
|
]);
|
|
|
|
FindingException::query()->create([
|
|
'workspace_id' => (int) $alphaTenant->workspace_id,
|
|
'managed_environment_id' => (int) $alphaTenant->getKey(),
|
|
'finding_id' => (int) $exceptionFinding->getKey(),
|
|
'requested_by_user_id' => (int) $user->getKey(),
|
|
'owner_user_id' => (int) $user->getKey(),
|
|
'status' => FindingException::STATUS_PENDING,
|
|
'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT,
|
|
'request_reason' => 'Governance home exception review',
|
|
'requested_at' => now()->subDay(),
|
|
'review_due_at' => now()->addDay(),
|
|
'evidence_summary' => ['reference_count' => 0],
|
|
]);
|
|
|
|
OperationRun::factory()
|
|
->forTenant($alphaTenant)
|
|
->create([
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Failed->value,
|
|
'completed_at' => now()->subMinute(),
|
|
]);
|
|
|
|
AlertDelivery::factory()->create([
|
|
'managed_environment_id' => null,
|
|
'workspace_id' => (int) $alphaTenant->workspace_id,
|
|
'status' => AlertDelivery::STATUS_FAILED,
|
|
'payload' => [
|
|
'title' => 'Delivery failed',
|
|
'body' => 'A notification destination failed.',
|
|
],
|
|
]);
|
|
|
|
$backupHealthResolver = app(TenantBackupHealthResolver::class);
|
|
$fingerprints = app(ManagedEnvironmentTriageReviewFingerprint::class);
|
|
$alphaBackupFingerprint = $fingerprints->forBackupHealth($backupHealthResolver->assess($alphaTenant));
|
|
|
|
expect($alphaBackupFingerprint)->not->toBeNull();
|
|
|
|
ManagedEnvironmentTriageReview::factory()
|
|
->for($alphaTenant)
|
|
->followUpNeeded()
|
|
->create([
|
|
'workspace_id' => (int) $alphaTenant->workspace_id,
|
|
'reviewed_by_user_id' => (int) $user->getKey(),
|
|
'concern_family' => PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH,
|
|
'review_fingerprint' => $alphaBackupFingerprint['fingerprint'],
|
|
'review_snapshot' => $alphaBackupFingerprint['snapshot'],
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $alphaTenant->workspace_id])
|
|
->get(GovernanceInbox::getUrl(panel: 'admin'))
|
|
->assertOk()
|
|
->assertSee('Assigned findings')
|
|
->assertSee('Findings intake')
|
|
->assertSee('Finding exceptions')
|
|
->assertSee('Operations follow-up')
|
|
->assertSee('Alert delivery failures')
|
|
->assertSee('Review follow-up')
|
|
->assertSee('Open my findings')
|
|
->assertSee('Open finding exceptions')
|
|
->assertSee('Open terminal follow-up')
|
|
->assertSee('Open alert deliveries')
|
|
->assertSee('Open customer review workspace');
|
|
});
|
|
|
|
it('renders honest empty states for tenant and family filtering on the governance inbox page', function (): void {
|
|
$alphaTenant = ManagedEnvironment::factory()->create([
|
|
'status' => 'active',
|
|
'name' => 'Alpha ManagedEnvironment',
|
|
'external_id' => 'alpha-tenant',
|
|
]);
|
|
[$user, $alphaTenant] = createUserWithTenant($alphaTenant, role: 'owner', workspaceRole: 'owner');
|
|
|
|
$bravoTenant = ManagedEnvironment::factory()->create([
|
|
'status' => 'active',
|
|
'workspace_id' => (int) $alphaTenant->workspace_id,
|
|
'name' => 'Bravo ManagedEnvironment',
|
|
'external_id' => 'bravo-tenant',
|
|
]);
|
|
|
|
$user->tenants()->syncWithoutDetaching([
|
|
(int) $bravoTenant->getKey() => ['role' => 'owner'],
|
|
]);
|
|
|
|
Finding::factory()
|
|
->for($bravoTenant)
|
|
->assignedTo((int) $user->getKey())
|
|
->create();
|
|
|
|
AlertDelivery::factory()->create([
|
|
'managed_environment_id' => null,
|
|
'workspace_id' => (int) $alphaTenant->workspace_id,
|
|
'status' => AlertDelivery::STATUS_FAILED,
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $alphaTenant->workspace_id])
|
|
->get(GovernanceInbox::getUrl(panel: 'admin').'?environment_id='.(string) $alphaTenant->getKey())
|
|
->assertOk()
|
|
->assertSee('This environment filter is hiding other visible attention')
|
|
->assertSee('Clear environment filter');
|
|
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $alphaTenant->workspace_id])
|
|
->get(GovernanceInbox::getUrl(panel: 'admin').'?environment_id='.(string) $alphaTenant->getKey().'&family=alert_delivery_failures')
|
|
->assertOk()
|
|
->assertSee('Alert delivery failures')
|
|
->assertSee('No failed alert deliveries match this environment filter right now.')
|
|
->assertDontSee('Open my findings');
|
|
});
|
|
|
|
it('omits the finding exceptions lane when the workspace capability is not visible', function (): void {
|
|
$tenant = ManagedEnvironment::factory()->create([
|
|
'status' => 'active',
|
|
'name' => 'Alpha ManagedEnvironment',
|
|
]);
|
|
[$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'readonly');
|
|
|
|
Finding::factory()
|
|
->for($tenant)
|
|
->assignedTo((int) $user->getKey())
|
|
->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
]);
|
|
|
|
$exceptionFinding = Finding::factory()
|
|
->for($tenant)
|
|
->riskAccepted()
|
|
->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
]);
|
|
|
|
FindingException::query()->create([
|
|
'workspace_id' => (int) $tenant->workspace_id,
|
|
'managed_environment_id' => (int) $tenant->getKey(),
|
|
'finding_id' => (int) $exceptionFinding->getKey(),
|
|
'requested_by_user_id' => (int) $user->getKey(),
|
|
'owner_user_id' => (int) $user->getKey(),
|
|
'status' => FindingException::STATUS_PENDING,
|
|
'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT,
|
|
'request_reason' => 'Hidden exception lane',
|
|
'requested_at' => now()->subDay(),
|
|
'review_due_at' => now()->addDay(),
|
|
'evidence_summary' => ['reference_count' => 0],
|
|
]);
|
|
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
|
|
->get(GovernanceInbox::getUrl(panel: 'admin'))
|
|
->assertOk()
|
|
->assertSee('Assigned findings')
|
|
->assertDontSee('Finding exceptions')
|
|
->assertDontSee('Hidden exception lane');
|
|
});
|