11, 'granted_count' => 7, ], operationRunCount: 0, )); expect(app(TenantReviewReadinessGate::class)->canPublish($review))->toBeFalse() ->and($review->publishBlockers())->not->toBeEmpty(); $truth = app(ArtifactTruthPresenter::class)->forTenantReview($review); expect($truth->artifactExistence)->toBe('created') ->and($truth->publicationReadiness)->toBe('blocked') ->and($truth->primaryLabel)->toBe('Publication blocked') ->and($truth->nextStepText())->toBe('Resolve the review blockers before publication'); expect(fn () => app(TenantReviewLifecycleService::class)->publish($review, $user)) ->toThrow(\InvalidArgumentException::class); }); it('publishes ready tenant reviews and archives them without mutating the published evidence history', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $review = $this->makeArtifactTruthReview( tenant: $tenant, user: $user, snapshot: $this->makeArtifactTruthEvidenceSnapshot($tenant), reviewOverrides: [ 'status' => TenantReviewStatus::Ready->value, 'completeness_state' => TenantReviewCompletenessState::Complete->value, ], summaryOverrides: [ 'publish_blockers' => [], 'section_state_counts' => [ 'complete' => 6, 'partial' => 0, 'missing' => 0, 'stale' => 0, ], ], ); $published = app(TenantReviewLifecycleService::class)->publish($review, $user); $publishedAt = $published->published_at?->toIso8601String(); expect($published->status)->toBe(TenantReviewStatus::Published->value) ->and($published->published_by_user_id)->toBe((int) $user->getKey()) ->and($publishedAt)->not->toBeNull(); $publishedTruth = app(ArtifactTruthPresenter::class)->forTenantReview($published); $archived = app(TenantReviewLifecycleService::class)->archive($published, $user); $archivedTruth = app(ArtifactTruthPresenter::class)->forTenantReview($archived); expect($archived->status)->toBe(TenantReviewStatus::Archived->value) ->and($archived->archived_at)->not->toBeNull() ->and($archived->published_at?->toIso8601String())->toBe($publishedAt) ->and($publishedTruth->publicationReadiness)->toBe('publishable') ->and($publishedTruth->nextStepText())->toBe('No action needed') ->and($archivedTruth->artifactExistence)->toBe('historical_only') ->and($archivedTruth->publicationReadiness)->toBe('internal_only') ->and($archivedTruth->nextStepText())->toBe('No action needed'); }); it('keeps stale published reviews internal only even when the lifecycle status is published', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $snapshot = seedStaleTenantReviewEvidence($tenant); $review = $this->makeArtifactTruthReview( tenant: $tenant, user: $user, snapshot: $snapshot, reviewOverrides: [ 'status' => TenantReviewStatus::Published->value, 'published_at' => now(), 'published_by_user_id' => (int) $user->getKey(), 'completeness_state' => TenantReviewCompletenessState::Complete->value, ], summaryOverrides: [ 'publish_blockers' => [], 'section_state_counts' => [ 'complete' => 6, 'partial' => 0, 'missing' => 0, 'stale' => 0, ], ], ); $truth = app(ArtifactTruthPresenter::class)->forTenantReview($review); expect($truth->freshnessState)->toBe('stale') ->and($truth->publicationReadiness)->toBe('internal_only') ->and($truth->primaryLabel)->toBe('Internal only') ->and($truth->nextStepText())->toBe('Refresh the evidence basis before publishing this review'); }); it('downgrades structurally ready reviews with partial evidence to internal only', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $snapshot = seedPartialTenantReviewEvidence($tenant); $review = $this->makeArtifactTruthReview( tenant: $tenant, user: $user, snapshot: $snapshot, reviewOverrides: [ 'status' => TenantReviewStatus::Ready->value, 'completeness_state' => TenantReviewCompletenessState::Complete->value, ], summaryOverrides: [ 'publish_blockers' => [], 'section_state_counts' => [ 'complete' => 6, 'partial' => 0, 'missing' => 0, 'stale' => 0, ], ], ); $truth = app(ArtifactTruthPresenter::class)->forTenantReview($review); expect($truth->contentState)->toBe('partial') ->and($truth->freshnessState)->toBe('current') ->and($truth->publicationReadiness)->toBe('internal_only') ->and($truth->primaryLabel)->toBe('Internal only') ->and($truth->nextStepText())->toBe('Complete the evidence basis before publishing this review'); });