create([ 'status' => 'active', 'name' => 'Alpha ManagedEnvironment', ]); [$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'owner'); $finding = Finding::factory()->for($tenant)->create([ 'workspace_id' => (int) $tenant->workspace_id, ]); $exception = 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' => (int) $user->getKey(), 'status' => FindingException::STATUS_PENDING, 'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT, 'request_reason' => 'Read only boundary test', 'requested_at' => now()->subDay(), 'review_due_at' => now()->addDay(), 'evidence_summary' => ['reference_count' => 0], ]); $decision = $exception->decisions()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'managed_environment_id' => (int) $tenant->getKey(), 'actor_user_id' => (int) $user->getKey(), 'decision_type' => FindingExceptionDecision::TYPE_REQUESTED, 'reason' => 'Read only boundary test', 'metadata' => [], 'decided_at' => now()->subDay(), ]); $exception->forceFill(['current_decision_id' => (int) $decision->getKey()])->save(); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get(DecisionRegister::getUrl(panel: 'admin')) ->assertOk() ->assertSee('Open decision') ->assertDontSee('Approve exception') ->assertDontSee('Reject exception') ->assertDontSee('Renew exception') ->assertDontSee('Revoke exception'); }); it('omits terminal decisions outside the 30 calendar day recently closed window', function (): void { $tenant = ManagedEnvironment::factory()->create([ 'status' => 'active', 'name' => 'Alpha ManagedEnvironment', ]); [$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'owner'); $createTerminalException = function (string $status, string $reason, int $daysAgo) use ($tenant, $user): FindingException { $finding = Finding::factory()->for($tenant)->create([ 'workspace_id' => (int) $tenant->workspace_id, ]); $exception = 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' => (int) $user->getKey(), 'status' => $status, 'current_validity_state' => $status === FindingException::STATUS_REJECTED ? FindingException::VALIDITY_REJECTED : FindingException::VALIDITY_REVOKED, 'request_reason' => 'Recently closed boundary test', 'review_due_at' => now()->subDays($daysAgo + 1), 'rejected_at' => $status === FindingException::STATUS_REJECTED ? now()->subDays($daysAgo) : null, 'revoked_at' => $status === FindingException::STATUS_REVOKED ? now()->subDays($daysAgo) : null, 'evidence_summary' => ['reference_count' => 0], ]); $decision = $exception->decisions()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'managed_environment_id' => (int) $tenant->getKey(), 'actor_user_id' => (int) $user->getKey(), 'decision_type' => $status === FindingException::STATUS_REJECTED ? FindingExceptionDecision::TYPE_REJECTED : FindingExceptionDecision::TYPE_REVOKED, 'reason' => $reason, 'metadata' => [], 'decided_at' => now()->subDays($daysAgo), ]); $exception->forceFill(['current_decision_id' => (int) $decision->getKey()])->save(); return $exception; }; $createTerminalException(FindingException::STATUS_REJECTED, 'Recent closure reason', 2); $createTerminalException(FindingException::STATUS_REVOKED, 'Old closure reason', 45); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get(DecisionRegister::getUrl(panel: 'admin', parameters: ['register_state' => 'recently_closed'])) ->assertOk() ->assertSee('Recent closure reason') ->assertDontSee('Old closure reason'); }); it('does not leak cross-workspace evidence artifact links from the decision register', function (): void { $tenant = ManagedEnvironment::factory()->create([ 'status' => 'active', 'name' => 'Visible ManagedEnvironment', ]); [$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'owner'); $otherTenant = ManagedEnvironment::factory()->create([ 'status' => 'active', 'name' => 'Other workspace ManagedEnvironment', ]); $otherSnapshot = EvidenceSnapshot::query()->create([ 'workspace_id' => (int) $otherTenant->workspace_id, 'managed_environment_id' => (int) $otherTenant->getKey(), 'status' => EvidenceSnapshotStatus::Active->value, 'completeness_state' => EvidenceCompletenessState::Complete->value, 'summary' => ['finding_count' => 1], 'generated_at' => now(), ]); $finding = Finding::factory()->for($tenant)->create([ 'workspace_id' => (int) $tenant->workspace_id, ]); $exception = 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' => (int) $user->getKey(), 'status' => FindingException::STATUS_PENDING, 'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT, 'request_reason' => 'Cross workspace proof link test', 'requested_at' => now()->subDay(), 'review_due_at' => now()->addDay(), 'evidence_summary' => ['reference_count' => 1], ]); $decision = $exception->decisions()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'managed_environment_id' => (int) $tenant->getKey(), 'actor_user_id' => (int) $user->getKey(), 'decision_type' => FindingExceptionDecision::TYPE_REQUESTED, 'reason' => 'Cross workspace proof link test', 'metadata' => [], 'decided_at' => now()->subDay(), ]); $exception->forceFill(['current_decision_id' => (int) $decision->getKey()])->save(); $exception->evidenceReferences()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'managed_environment_id' => (int) $tenant->getKey(), 'source_type' => 'evidence_snapshot', 'source_id' => (string) $otherSnapshot->getKey(), 'label' => 'Other workspace snapshot', 'summary_payload' => [], ]); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get(DecisionRegister::getUrl(panel: 'admin')) ->assertOk() ->assertSee('1 proof item') ->assertSee('View proof') ->assertDontSee('View evidence') ->assertDontSee('/admin/t', false); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get(EvidenceSnapshotResource::getUrl('view', ['record' => $otherSnapshot], tenant: $otherTenant, panel: 'admin')) ->assertNotFound(); }); it('does not leak cross-environment evidence artifact links from the decision register', function (): void { $tenant = ManagedEnvironment::factory()->create([ 'status' => 'active', 'name' => 'Visible ManagedEnvironment', ]); [$user, $tenant] = createUserWithTenant($tenant, role: 'owner', workspaceRole: 'owner'); $otherTenant = ManagedEnvironment::factory()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'status' => 'active', 'name' => 'Other environment ManagedEnvironment', ]); $otherSnapshot = EvidenceSnapshot::query()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'managed_environment_id' => (int) $otherTenant->getKey(), 'status' => EvidenceSnapshotStatus::Active->value, 'completeness_state' => EvidenceCompletenessState::Complete->value, 'summary' => ['finding_count' => 1], 'generated_at' => now(), ]); $finding = Finding::factory()->for($tenant)->create([ 'workspace_id' => (int) $tenant->workspace_id, ]); $exception = 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' => (int) $user->getKey(), 'status' => FindingException::STATUS_PENDING, 'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT, 'request_reason' => 'Cross environment proof link test', 'requested_at' => now()->subDay(), 'review_due_at' => now()->addDay(), 'evidence_summary' => ['reference_count' => 1], ]); $decision = $exception->decisions()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'managed_environment_id' => (int) $tenant->getKey(), 'actor_user_id' => (int) $user->getKey(), 'decision_type' => FindingExceptionDecision::TYPE_REQUESTED, 'reason' => 'Cross environment proof link test', 'metadata' => [], 'decided_at' => now()->subDay(), ]); $exception->forceFill(['current_decision_id' => (int) $decision->getKey()])->save(); $exception->evidenceReferences()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'managed_environment_id' => (int) $tenant->getKey(), 'source_type' => 'evidence_snapshot', 'source_id' => (string) $otherSnapshot->getKey(), 'label' => 'Other environment snapshot', 'summary_payload' => [], ]); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get(DecisionRegister::getUrl(panel: 'admin')) ->assertOk() ->assertSee('1 proof item') ->assertSee('View proof') ->assertDontSee('View evidence') ->assertDontSee('/admin/t', false); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get(EvidenceSnapshotResource::getUrl('view', ['record' => $otherSnapshot], tenant: $otherTenant, panel: 'admin')) ->assertNotFound(); });