create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'report_type' => StoredReport::REPORT_TYPE_PERMISSION_POSTURE, 'fingerprint' => 'permission-posture-v1', 'created_at' => now()->subDay(), 'updated_at' => now()->subDay(), ]); $current = StoredReport::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'report_type' => StoredReport::REPORT_TYPE_PERMISSION_POSTURE, 'fingerprint' => 'permission-posture-v2', 'previous_fingerprint' => 'permission-posture-v1', 'created_at' => now(), 'updated_at' => now(), ]); $historicalTruth = app(ArtifactTruthPresenter::class)->for($historical->fresh()); $currentTruth = app(ArtifactTruthPresenter::class)->for($current->fresh()); expect($historicalTruth)->not->toBeNull() ->and($currentTruth)->not->toBeNull(); $historicalState = $historicalTruth?->toArray(); $currentState = $currentTruth?->toArray(); expect($historicalState['displayReference'] ?? null) ->toContain('Stored report') ->toContain('#'.$historical->getKey()) ->and($historicalState['integrityAnchor'] ?? null)->toBe('permission-posture-v1') ->and($historicalState['lifecycleState'] ?? null)->toBe('historical') ->and($historicalState['retentionState'] ?? null)->toBe('retained') ->and($currentState['displayReference'] ?? null)->toContain('#'.$current->getKey()) ->and($currentState['integrityAnchor'] ?? null)->toBe('permission-posture-v2') ->and($currentState['lifecycleState'] ?? null)->toBe('current') ->and($currentState['retentionState'] ?? null)->toBe('retained'); }); it('maps accepted-risk decision history into current and superseded artifact truth', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $finding = Finding::factory()->for($tenant)->create(); $exception = FindingException::query()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'finding_id' => (int) $finding->getKey(), 'requested_by_user_id' => (int) $user->getKey(), 'owner_user_id' => (int) $user->getKey(), 'status' => FindingException::STATUS_ACTIVE, 'current_validity_state' => FindingException::VALIDITY_VALID, 'request_reason' => 'Approve temporary exception', 'requested_at' => now()->subDays(10), 'approved_at' => now()->subDays(9), 'effective_from' => now()->subDays(9), 'expires_at' => now()->addDays(14), 'review_due_at' => now()->addWeek(), 'evidence_summary' => ['reference_count' => 1], ]); $requestedDecision = $exception->decisions()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'actor_user_id' => (int) $user->getKey(), 'decision_type' => FindingExceptionDecision::TYPE_REQUESTED, 'reason' => 'Approve temporary exception', 'metadata' => [], 'decided_at' => now()->subDays(10), ]); $approvedDecision = $exception->decisions()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'actor_user_id' => (int) $user->getKey(), 'decision_type' => FindingExceptionDecision::TYPE_APPROVED, 'reason' => 'Approved for a bounded review period', 'metadata' => [], 'effective_from' => now()->subDays(9), 'expires_at' => now()->addDays(14), 'decided_at' => now()->subDays(9), ]); $exception->forceFill(['current_decision_id' => (int) $approvedDecision->getKey()])->save(); $requestedTruth = app(ArtifactTruthPresenter::class)->for($requestedDecision->fresh('exception.currentDecision')); $approvedTruth = app(ArtifactTruthPresenter::class)->for($approvedDecision->fresh('exception.currentDecision')); expect($requestedTruth)->not->toBeNull() ->and($approvedTruth)->not->toBeNull(); $requestedState = $requestedTruth?->toArray(); $approvedState = $approvedTruth?->toArray(); expect($requestedState['displayReference'] ?? null) ->toContain('Accepted-risk decision') ->toContain('#'.$requestedDecision->getKey()) ->and($requestedState['lifecycleState'] ?? null)->toBe('superseded') ->and($requestedState['retentionState'] ?? null)->toBe('retained') ->and($approvedState['displayReference'] ?? null)->toContain('#'.$approvedDecision->getKey()) ->and($approvedState['lifecycleState'] ?? null)->toBe('current') ->and($approvedState['retentionState'] ?? null)->toBe('retained'); }); it('keeps expired direct access separate from historical lifecycle on review packs', function (): void { $tenant = Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); $pack = ReviewPack::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'initiated_by_user_id' => (int) $user->getKey(), 'status' => ReviewPackStatus::Expired->value, 'fingerprint' => 'review-pack-expired', 'sha256' => 'sha-expired-pack', 'generated_at' => now()->subDays(3), 'expires_at' => now()->subDay(), ]); $truth = app(ArtifactTruthPresenter::class)->forReviewPack($pack->fresh()); $state = $truth->toArray(); expect($state['displayReference'] ?? null)->not->toBeNull() ->and($state['displayReference'])->toContain('Review pack') ->and($state['displayReference'])->toContain('#'.$pack->getKey()) ->and($state['integrityAnchor'] ?? null)->toBe('sha-expired-pack') ->and($state['lifecycleState'] ?? null)->toBe('historical') ->and($state['retentionState'] ?? null)->toBe('expired_direct_access'); });