create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); setAdminPanelContext($tenant); spec393EvidenceSnapshot($tenant, [ 'completeness_state' => EvidenceCompletenessState::Partial->value, 'summary' => ['missing_dimensions' => 1, 'stale_dimensions' => 0], ]); $anchor = app(EvidenceAnchorResolver::class)->currentForScope($tenant->workspace, $tenant, $user); expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE) ->and($anchor->state)->toBe(EvidenceAnchorResult::STATE_NEEDS_ATTENTION) ->and($anchor->evidenceSnapshotId)->toBeNull() ->and($anchor->targetRoute)->toBeNull() ->and($anchor->canLink)->toBeFalse(); }); it('does not fall back to evidence from another environment in the workspace', function (): void { $tenantA = ManagedEnvironment::factory()->create(); [$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner'); $tenantB = ManagedEnvironment::factory()->create(['workspace_id' => (int) $tenantA->workspace_id]); createUserWithTenant(tenant: $tenantB, user: $user, role: 'owner'); setAdminPanelContext($tenantA); spec393EvidenceSnapshot($tenantB); $anchor = app(EvidenceAnchorResolver::class)->currentForScope($tenantA->workspace, $tenantA, $user); expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE) ->and($anchor->state)->toBe(EvidenceAnchorResult::STATE_NOT_CONFIGURED) ->and($anchor->targetRoute)->toBeNull(); }); it('excludes expired current evidence from current scope anchors', function (): void { $tenant = ManagedEnvironment::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); setAdminPanelContext($tenant); spec393EvidenceSnapshot($tenant, [ 'expires_at' => now()->subMinute(), ]); $anchor = app(EvidenceAnchorResolver::class)->currentForScope($tenant->workspace, $tenant, $user); expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE) ->and($anchor->state)->toBe(EvidenceAnchorResult::STATE_NEEDS_ATTENTION) ->and($anchor->evidenceSnapshotId)->toBeNull() ->and($anchor->targetRoute)->toBeNull() ->and($anchor->canLink)->toBeFalse(); }); it('excludes superseded evidence from current scope anchors', function (): void { $tenant = ManagedEnvironment::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); setAdminPanelContext($tenant); spec393EvidenceSnapshot($tenant, [ 'status' => EvidenceSnapshotStatus::Superseded->value, ]); $anchor = app(EvidenceAnchorResolver::class)->currentForScope($tenant->workspace, $tenant, $user); expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE) ->and($anchor->state)->toBe(EvidenceAnchorResult::STATE_NEEDS_ATTENTION) ->and($anchor->evidenceSnapshotId)->toBeNull() ->and($anchor->targetRoute)->toBeNull(); }); it('excludes wrong workspace evidence even when the environment id matches', function (): void { $tenant = ManagedEnvironment::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); $foreignWorkspace = Workspace::factory()->create(); setAdminPanelContext($tenant); $snapshot = spec393EvidenceSnapshot($tenant); EvidenceSnapshot::withoutEvents(function () use ($snapshot, $foreignWorkspace): void { $snapshot->forceFill([ 'workspace_id' => (int) $foreignWorkspace->getKey(), ])->save(); }); $anchor = app(EvidenceAnchorResolver::class)->currentForScope($tenant->workspace, $tenant, $user); expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE) ->and($anchor->state)->toBe(EvidenceAnchorResult::STATE_NOT_CONFIGURED) ->and($anchor->evidenceSnapshotId)->toBeNull() ->and($anchor->targetRoute)->toBeNull(); }); it('keeps released review evidence pinned instead of falling forward to current evidence', function (): void { $tenant = ManagedEnvironment::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); setAdminPanelContext($tenant); $releasedSnapshot = spec393EvidenceSnapshot($tenant, [ 'fingerprint' => 'released-snapshot', 'generated_at' => now()->subDays(3), ]); $review = EnvironmentReview::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'evidence_snapshot_id' => (int) $releasedSnapshot->getKey(), 'status' => EnvironmentReviewStatus::Published->value, 'published_at' => now()->subDays(2), 'published_by_user_id' => (int) $user->getKey(), ]); $pack = ReviewPack::factory()->ready()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'environment_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $releasedSnapshot->getKey(), 'initiated_by_user_id' => (int) $user->getKey(), ]); $releasedSnapshot->forceFill([ 'status' => EvidenceSnapshotStatus::Superseded->value, ])->save(); spec393EvidenceSnapshot($tenant, [ 'fingerprint' => 'new-current-snapshot', 'generated_at' => now(), ]); $releaseAnchor = app(EvidenceAnchorResolver::class)->forReviewPackRelease($pack->fresh(), $user); $customerAnchor = app(EvidenceAnchorResolver::class)->forCustomerWorkspace($review->fresh(), $user); expect($releaseAnchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_REVIEW_RELEASED_EVIDENCE) ->and($releaseAnchor->evidenceSnapshotId)->toBe((int) $releasedSnapshot->getKey()) ->and($releaseAnchor->isReleaseBound)->toBeTrue() ->and($releaseAnchor->isCurrent)->toBeFalse() ->and($releaseAnchor->targetRoute)->toBe(EvidenceSnapshotResource::getUrl('view', ['record' => $releasedSnapshot], tenant: $tenant, panel: 'admin')) ->and($customerAnchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_CUSTOMER_SAFE_EVIDENCE_SUMMARY) ->and($customerAnchor->evidenceSnapshotId)->toBeNull() ->and($customerAnchor->targetRoute)->toBeNull() ->and($customerAnchor->isCustomerSafe)->toBeTrue(); }); it('fails closed when customer workspace review pack evidence does not match the pack scope', function (): void { $tenantA = ManagedEnvironment::factory()->create(); [$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner'); $tenantB = ManagedEnvironment::factory()->create(['workspace_id' => (int) $tenantA->workspace_id]); createUserWithTenant(tenant: $tenantB, user: $user, role: 'owner'); setAdminPanelContext($tenantA); $wrongScopeSnapshot = spec393EvidenceSnapshot($tenantB); $review = EnvironmentReview::factory()->create([ 'managed_environment_id' => (int) $tenantA->getKey(), 'workspace_id' => (int) $tenantA->workspace_id, 'evidence_snapshot_id' => (int) $wrongScopeSnapshot->getKey(), 'status' => EnvironmentReviewStatus::Published->value, 'published_at' => now(), 'published_by_user_id' => (int) $user->getKey(), ]); $pack = ReviewPack::factory()->ready()->create([ 'managed_environment_id' => (int) $tenantA->getKey(), 'workspace_id' => (int) $tenantA->workspace_id, 'environment_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $wrongScopeSnapshot->getKey(), 'initiated_by_user_id' => (int) $user->getKey(), ]); $anchor = app(EvidenceAnchorResolver::class)->forCustomerWorkspace($pack->fresh(), $user); expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE) ->and($anchor->state)->toBe(EvidenceAnchorResult::STATE_BLOCKED) ->and($anchor->isCustomerSafe)->toBeFalse() ->and($anchor->evidenceSnapshotId)->toBeNull() ->and($anchor->targetRoute)->toBeNull() ->and($anchor->canLink)->toBeFalse() ->and($anchor->primaryReason)->toContain('scope does not match'); }); it('keeps draft review pack evidence internal and not customer safe', function (): void { $tenant = ManagedEnvironment::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); setAdminPanelContext($tenant); $snapshot = spec393EvidenceSnapshot($tenant); $pack = ReviewPack::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'initiated_by_user_id' => (int) $user->getKey(), ]); $anchor = app(EvidenceAnchorResolver::class)->forReviewPackDraft($pack->fresh(), $user); expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_REVIEW_DRAFT_EVIDENCE) ->and($anchor->isCustomerSafe)->toBeFalse() ->and($anchor->isTechnicalOnly)->toBeTrue() ->and($anchor->displayLabel)->toBe('Draft review evidence'); }); it('gates technical evidence detail links by evidence view authorization', function (): void { $tenant = ManagedEnvironment::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); $snapshot = spec393EvidenceSnapshot($tenant); setAdminPanelContext($tenant); $authorizedAnchor = app(EvidenceAnchorResolver::class)->technicalDetail($snapshot, $user); expect($authorizedAnchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_TECHNICAL_EVIDENCE_DETAIL) ->and($authorizedAnchor->displayLabel)->toBe('View internal evidence details') ->and($authorizedAnchor->canViewTechnicalDetail)->toBeTrue() ->and($authorizedAnchor->targetRoute)->toBe(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin')); Gate::define(Capabilities::EVIDENCE_VIEW, fn (User $actor, ManagedEnvironment $environment): bool => false); $deniedAnchor = app(EvidenceAnchorResolver::class)->technicalDetail($snapshot, $user); expect($deniedAnchor->canViewTechnicalDetail)->toBeFalse() ->and($deniedAnchor->targetRoute)->toBeNull() ->and($deniedAnchor->canLink)->toBeFalse(); }); it('keeps current evidence selection order explicit and deterministic', function (): void { $tenant = ManagedEnvironment::factory()->create(); $resolver = app(EvidenceAnchorResolver::class); $orders = $resolver->currentSnapshotQuery($tenant->workspace, $tenant)->getQuery()->orders; expect($orders[0]['type'] ?? null)->toBe('Raw') ->and($orders[0]['sql'] ?? null)->toBe('generated_at IS NULL') ->and($orders[1]['column'] ?? null)->toBe('generated_at') ->and($orders[1]['direction'] ?? null)->toBe('desc') ->and($orders[2]['column'] ?? null)->toBe('id') ->and($orders[2]['direction'] ?? null)->toBe('desc'); }); /** * @param array $attributes */ function spec393EvidenceSnapshot(ManagedEnvironment $tenant, array $attributes = []): EvidenceSnapshot { return EvidenceSnapshot::query()->create(array_replace([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => EvidenceSnapshotStatus::Active->value, 'completeness_state' => EvidenceCompletenessState::Complete->value, 'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0], 'fingerprint' => 'spec393-'.str()->uuid()->toString(), 'generated_at' => now(), ], $attributes)); }