## Summary - enforce the canonical workspace/environment scope contract for workspace hubs and environment-owned surfaces - replace first-party Operations deep links that leaked Filament `tableFilters[...]` internals with stable product-level query behavior - add the sidebar scope indicator and split environment-page navigation into explicit `Workspace-wide` and `Workspace admin` groups - remove redundant tenantless `All environments` scope badges from workspace-wide pages while preserving explicit environment filter affordances - include the Spec 338 artifacts, guard tests, and browser smoke coverage for the new contract ## Validation - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Navigation/Spec338EnvironmentSidebarSeparationTest.php tests/Feature/Navigation/Spec338OperationRunLinksQueryContractTest.php tests/Feature/Navigation/Spec338SidebarScopeIndicatorTest.php tests/Feature/Filament/PanelNavigationSegregationTest.php` - `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec338ScopeContractSmokeTest.php --compact` ## Notes - Livewire v4 compliance unchanged - Filament provider registration remains in `bootstrap/providers.php` - no destructive action behavior changed - no migrations, env var changes, or new Filament asset registration Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #409
363 lines
16 KiB
PHP
363 lines
16 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Monitoring\EvidenceOverview;
|
|
use App\Models\EnvironmentReview;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ReviewPack;
|
|
use App\Models\StoredReport;
|
|
use App\Models\User;
|
|
use App\Support\EnvironmentReviewCompletenessState;
|
|
use App\Support\EnvironmentReviewStatus;
|
|
use App\Support\Evidence\EvidenceCompletenessState;
|
|
use App\Support\Evidence\EvidenceSnapshotStatus;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\OperationRunType;
|
|
use App\Support\ReviewPackStatus;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Livewire\Livewire;
|
|
|
|
it('renders missing evidence as the first evidence readiness blocker', function (): void {
|
|
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
|
|
$component = spec337EvidenceOverviewLivewire($user, $environment)
|
|
->assertSee('Is this evidence package ready for customer or auditor consumption?')
|
|
->assertSee('Evidence snapshot required')
|
|
->assertSee('Review pack output cannot be trusted or exported yet.')
|
|
->assertSee('Generate evidence snapshot')
|
|
->assertSee('Evidence readiness flow')
|
|
->assertSee('Evidence proof')
|
|
->assertSee('Diagnostics - Collapsed')
|
|
->assertDontSee('raw payload should stay hidden')
|
|
->assertDontSee('provider response should stay hidden')
|
|
->assertDontSee('stack trace should stay hidden');
|
|
|
|
$content = $component->html();
|
|
|
|
spec337AssertFlowStep($content, 'Source data selected', 'Available', false);
|
|
spec337AssertFlowStep($content, 'Evidence snapshot', 'Missing', true);
|
|
spec337AssertFlowStep($content, 'Stored report', 'Unavailable', false);
|
|
spec337AssertFlowStep($content, 'Review pack', 'Unavailable', false);
|
|
spec337AssertFlowStep($content, 'Customer-safe output', 'Not ready', false);
|
|
spec337AssertFlowStep($content, 'Export / delivery', 'Unavailable', false);
|
|
|
|
expect(substr_count($content, 'data-testid="evidence-readiness-step"'))->toBe(6)
|
|
->and($content)->toContain('data-testid="evidence-readiness-flow" class="@container')
|
|
->and($content)->not->toContain('data-testid="evidence-disclosure-diagnostics" open');
|
|
});
|
|
|
|
it('renders stored report missing without inventing review-pack or export readiness', function (): void {
|
|
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
|
|
spec337CreateEvidenceSnapshot($environment);
|
|
|
|
$component = spec337EvidenceOverviewLivewire($user, $environment)
|
|
->assertSee('Stored report required')
|
|
->assertSee('Evidence snapshot exists, but no stored report is available for this review output.')
|
|
->assertSee('Open evidence snapshot')
|
|
->assertDontSee('Review pack export available')
|
|
->assertDontSee('Customer-safe output ready')
|
|
->assertDontSee('Download export');
|
|
|
|
$content = $component->html();
|
|
|
|
spec337AssertFlowStep($content, 'Evidence snapshot', 'Available', false);
|
|
spec337AssertFlowStep($content, 'Stored report', 'Missing', true);
|
|
spec337AssertFlowStep($content, 'Review pack', 'Unavailable', false);
|
|
spec337AssertFlowStep($content, 'Customer-safe output', 'Not ready', false);
|
|
spec337AssertFlowStep($content, 'Export / delivery', 'Unavailable', false);
|
|
});
|
|
|
|
it('renders review pack required with capability-aware primary action', function (): void {
|
|
[$owner, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
[$readonly] = createUserWithTenant(tenant: $environment, role: 'readonly', workspaceRole: 'readonly');
|
|
|
|
$snapshot = spec337CreateEvidenceSnapshot($environment);
|
|
spec337CreateStoredReport($environment);
|
|
|
|
$ownerComponent = spec337EvidenceOverviewLivewire($owner, $environment)
|
|
->assertSee('Review pack required')
|
|
->assertSee('Stored report exists, but a review pack has not been generated.')
|
|
->assertSee('Generate review pack')
|
|
->assertDontSee('Customer-safe output ready')
|
|
->assertDontSee('Review pack export available');
|
|
|
|
spec337AssertFlowStep($ownerComponent->html(), 'Review pack', 'Required', true);
|
|
|
|
spec337EvidenceOverviewLivewire($readonly, $environment)
|
|
->assertSee('Review pack required')
|
|
->assertDontSee('Generate review pack')
|
|
->assertSee('Open evidence snapshot')
|
|
->assertSee((string) $snapshot->tenant?->name);
|
|
});
|
|
|
|
it('renders repo-backed customer-safe and export-ready output without broad readiness claims', function (): void {
|
|
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
|
|
$snapshot = spec337CreateEvidenceSnapshot($environment);
|
|
spec337CreateStoredReport($environment);
|
|
[$review, $pack] = spec337CreatePublishedReviewWithReadyPack($environment, $user, $snapshot);
|
|
|
|
$component = spec337EvidenceOverviewLivewire($user, $environment)
|
|
->assertSee('Review pack export available')
|
|
->assertSee('A generated export artifact is available for authorized download.')
|
|
->assertSee('external sharing remains governed by workspace policy')
|
|
->assertSee('Download export')
|
|
->assertSee('Customer-safe output')
|
|
->assertSee('Ready')
|
|
->assertSee('Review pack contents / coverage')
|
|
->assertSee('Findings included')
|
|
->assertSee('Evidence dimensions')
|
|
->assertDontSee('Auditor-ready')
|
|
->assertDontSee('environment is healthy')
|
|
->assertDontSee('compliant');
|
|
|
|
$content = $component->html();
|
|
|
|
spec337AssertFlowStep($content, 'Evidence snapshot', 'Available', false);
|
|
spec337AssertFlowStep($content, 'Stored report', 'Available', false);
|
|
spec337AssertFlowStep($content, 'Review pack', 'Available', false);
|
|
spec337AssertFlowStep($content, 'Customer-safe output', 'Ready', false);
|
|
spec337AssertFlowStep($content, 'Export / delivery', 'Available', true);
|
|
|
|
expect($review->current_export_review_pack_id)->toBe($pack->getKey());
|
|
});
|
|
|
|
it('renders generating and failed operation proof separately from usable output', function (): void {
|
|
[$user, $generatingEnvironment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
$generatingRun = OperationRun::factory()->forTenant($generatingEnvironment)->create([
|
|
'type' => OperationRunType::EvidenceSnapshotGenerate->value,
|
|
'status' => OperationRunStatus::Running->value,
|
|
'outcome' => OperationRunOutcome::Pending->value,
|
|
'started_at' => now()->subMinute(),
|
|
'initiator_name' => 'Spec337 Operator',
|
|
]);
|
|
spec337CreateEvidenceSnapshot($generatingEnvironment, [
|
|
'operation_run_id' => (int) $generatingRun->getKey(),
|
|
'status' => EvidenceSnapshotStatus::Generating->value,
|
|
'generated_at' => null,
|
|
]);
|
|
|
|
$generatingComponent = spec337EvidenceOverviewLivewire($user, $generatingEnvironment)
|
|
->assertSee('Evidence generation in progress')
|
|
->assertSee('View operation progress')
|
|
->assertSee('Operation proof')
|
|
->assertSee('Generating')
|
|
->assertSee('Spec337 Operator')
|
|
->assertDontSee('Customer-safe output ready');
|
|
|
|
spec337AssertFlowStep($generatingComponent->html(), 'Evidence snapshot', 'Generating', true);
|
|
|
|
$failedEnvironment = createUserWithTenant(user: $user, role: 'owner', workspaceRole: 'manager')[1];
|
|
$failedRun = OperationRun::factory()->forTenant($failedEnvironment)->create([
|
|
'type' => OperationRunType::ReviewPackGenerate->value,
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Failed->value,
|
|
'started_at' => now()->subMinutes(3),
|
|
'completed_at' => now()->subMinute(),
|
|
'initiator_name' => 'Spec337 Reviewer',
|
|
]);
|
|
$failedSnapshot = spec337CreateEvidenceSnapshot($failedEnvironment);
|
|
spec337CreateStoredReport($failedEnvironment);
|
|
ReviewPack::factory()->failed()->create([
|
|
'managed_environment_id' => (int) $failedEnvironment->getKey(),
|
|
'workspace_id' => (int) $failedEnvironment->workspace_id,
|
|
'evidence_snapshot_id' => (int) $failedSnapshot->getKey(),
|
|
'operation_run_id' => (int) $failedRun->getKey(),
|
|
]);
|
|
|
|
$failedComponent = spec337EvidenceOverviewLivewire($user, $failedEnvironment)
|
|
->assertSee('Review pack generation failed')
|
|
->assertSee('Review operation')
|
|
->assertSee('Operation proof')
|
|
->assertSee('Failed')
|
|
->assertSee('Spec337 Reviewer')
|
|
->assertDontSee('Review pack export available');
|
|
|
|
spec337AssertFlowStep($failedComponent->html(), 'Review pack', 'Failed', true);
|
|
});
|
|
|
|
it('preserves workspace isolation and avoids legacy tenant aliases in rendered links', function (): void {
|
|
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
$foreignEnvironment = ManagedEnvironment::factory()->active()->create([
|
|
'name' => 'Spec337 Foreign Workspace Environment',
|
|
]);
|
|
spec337CreateEvidenceSnapshot($foreignEnvironment);
|
|
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id])
|
|
->get(route('admin.evidence.overview'))
|
|
->assertOk()
|
|
->assertDontSee('Spec337 Foreign Workspace Environment');
|
|
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id])
|
|
->get(route('admin.evidence.overview', ['environment_id' => (int) $foreignEnvironment->getKey()]))
|
|
->assertNotFound();
|
|
|
|
$content = spec337EvidenceOverviewLivewire($user, $environment)->html();
|
|
|
|
expect($content)
|
|
->not->toContain('/admin/t/')
|
|
->not->toContain('tenant_id=');
|
|
}
|
|
);
|
|
|
|
function spec337EvidenceOverviewLivewire(User $user, ManagedEnvironment $environment): mixed
|
|
{
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $environment->workspace_id);
|
|
setAdminPanelContext($environment);
|
|
|
|
return Livewire::withQueryParams([
|
|
'environment_id' => (int) $environment->getKey(),
|
|
])
|
|
->actingAs($user)
|
|
->test(EvidenceOverview::class);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
*/
|
|
function spec337CreateEvidenceSnapshot(ManagedEnvironment $environment, array $attributes = []): EvidenceSnapshot
|
|
{
|
|
$snapshot = EvidenceSnapshot::query()->create(array_replace([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'fingerprint' => sha1('spec337-snapshot-'.$environment->getKey().'-'.microtime(true)),
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
'summary' => [
|
|
'dimension_count' => 5,
|
|
'missing_dimensions' => 0,
|
|
'stale_dimensions' => 0,
|
|
'raw_payload' => 'raw payload should stay hidden',
|
|
'provider_response' => 'provider response should stay hidden',
|
|
'stack_trace' => 'stack trace should stay hidden',
|
|
],
|
|
'generated_at' => now(),
|
|
'expires_at' => now()->addDays(30),
|
|
], $attributes));
|
|
|
|
$snapshot->items()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'dimension_key' => 'permission_posture',
|
|
'state' => (string) ($snapshot->completeness_state ?: EvidenceCompletenessState::Complete->value),
|
|
'required' => true,
|
|
'source_kind' => 'stored_report',
|
|
'source_record_type' => StoredReport::class,
|
|
'source_record_id' => null,
|
|
'source_fingerprint' => null,
|
|
'measured_at' => now(),
|
|
'freshness_at' => now(),
|
|
'summary_payload' => [
|
|
'label' => 'Permission posture',
|
|
'state' => 'complete',
|
|
],
|
|
'sort_order' => 1,
|
|
]);
|
|
|
|
return $snapshot->load([
|
|
'tenant',
|
|
'operationRun',
|
|
'reviewPacks.operationRun',
|
|
'reviewPacks.environmentReview.currentExportReviewPack',
|
|
'items',
|
|
]);
|
|
}
|
|
|
|
function spec337CreateStoredReport(ManagedEnvironment $environment): StoredReport
|
|
{
|
|
return StoredReport::factory()->permissionPosture([
|
|
'raw_payload' => 'raw payload should stay hidden',
|
|
'provider_response' => 'provider response should stay hidden',
|
|
'stack_trace' => 'stack trace should stay hidden',
|
|
])->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'fingerprint' => sha1('spec337-report-'.$environment->getKey().'-'.microtime(true)),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @return array{0: EnvironmentReview, 1: ReviewPack}
|
|
*/
|
|
function spec337CreatePublishedReviewWithReadyPack(
|
|
ManagedEnvironment $environment,
|
|
User $user,
|
|
EvidenceSnapshot $snapshot,
|
|
): array {
|
|
$review = EnvironmentReview::query()->create([
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
'fingerprint' => sha1('spec337-review-'.$environment->getKey().'-'.microtime(true)),
|
|
'status' => EnvironmentReviewStatus::Published->value,
|
|
'completeness_state' => EnvironmentReviewCompletenessState::Complete->value,
|
|
'summary' => [
|
|
'governance_package' => [
|
|
'decision_summary' => [
|
|
'status' => 'ready',
|
|
'evidence_state' => EnvironmentReviewCompletenessState::Complete->value,
|
|
'decision_data_state' => 'complete',
|
|
],
|
|
],
|
|
],
|
|
'generated_at' => now()->subMinutes(5),
|
|
'published_at' => now()->subMinutes(3),
|
|
]);
|
|
|
|
$run = OperationRun::factory()->forTenant($environment)->create([
|
|
'type' => OperationRunType::ReviewPackGenerate->value,
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Succeeded->value,
|
|
'started_at' => now()->subMinutes(4),
|
|
'completed_at' => now()->subMinutes(2),
|
|
'initiator_name' => $user->name,
|
|
]);
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'environment_review_id' => (int) $review->getKey(),
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'status' => ReviewPackStatus::Ready->value,
|
|
'summary' => [
|
|
'finding_count' => 3,
|
|
'report_count' => 2,
|
|
'operation_count' => 1,
|
|
'section_count' => 4,
|
|
'evidence_resolution' => [
|
|
'required_dimensions' => [
|
|
'findings_summary',
|
|
'permission_posture',
|
|
'entra_admin_roles',
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
|
|
$review->forceFill([
|
|
'current_export_review_pack_id' => (int) $pack->getKey(),
|
|
])->save();
|
|
|
|
return [$review->refresh(), $pack->refresh()];
|
|
}
|
|
|
|
function spec337AssertFlowStep(string $content, string $label, string $state, bool $currentBlocker): void
|
|
{
|
|
$blocker = $currentBlocker ? 'true' : 'false';
|
|
|
|
expect($content)->toMatch(
|
|
'/data-step-label="'.preg_quote($label, '/').'"[\s\S]*?data-step-state="'.preg_quote($state, '/').'"[\s\S]*?data-step-current-blocker="'.$blocker.'"[\s\S]*?>\s*'.preg_quote($state, '/').'\s*</'
|
|
);
|
|
}
|