false, 'include_operations' => true, ]; $fingerprint = app(ReviewPackService::class)->computeFingerprintForReview($review, $options); $publishedRun = OperationRun::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'type' => 'environment.review_pack.generate', 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, ]); $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) $snapshot->getKey(), 'operation_run_id' => (int) $publishedRun->getKey(), 'initiated_by_user_id' => (int) $user->getKey(), 'fingerprint' => $fingerprint, 'options' => $options, 'summary' => [ 'finding_count' => 4, 'report_count' => 2, 'operation_count' => 3, ], ]); $run = OperationRun::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'user_id' => (int) $user->getKey(), 'initiator_name' => $user->name, 'type' => 'environment.review_pack.generate', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, 'created_at' => now()->subMinutes(25), 'context' => [ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'environment_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'include_pii' => $options['include_pii'], 'include_operations' => $options['include_operations'], ], ]); $change = app(AdapterRunReconciler::class)->reconcileOperationRun($run, false); expect($change['applied'] ?? null)->toBeTrue(); $run->refresh(); $pack->refresh(); expect($run->status)->toBe(OperationRunStatus::Completed->value) ->and($run->outcome)->toBe(OperationRunOutcome::Succeeded->value) ->and($run->reconciliationDecision())->toBe('reconciled_succeeded') ->and($run->reconciliationAdapter())->toBe('review_pack') ->and($run->reconciledRelatedReviewPackId())->toBe((int) $pack->getKey()) ->and($run->summary_counts)->toMatchArray([ 'finding_count' => 4, 'report_count' => 2, 'operation_count' => 3, ]) ->and($pack->status)->toBe(ReviewPackStatus::Ready->value) ->and($pack->fingerprint)->toBe($fingerprint); $this->actingAs($user); setAdminPanelContext($tenant); $expected = ReviewPackResource::getUrl('view', ['record' => $pack], tenant: $tenant); $links = OperationRunLinks::related($run->fresh(), $tenant); $sharedLinks = app(RelatedNavigationResolver::class)->operationLinks($run->fresh(), $tenant); $sharedEntry = collect(app(RelatedNavigationResolver::class)->detailEntries( CrossResourceNavigationMatrix::SOURCE_OPERATION_RUN, $run->fresh(), ))->firstWhere('key', 'review_pack'); $truth = app(ArtifactTruthPresenter::class)->forOperationRun($run->fresh()); expect($links['Review Pack'] ?? null)->toBe($expected) ->and($sharedLinks['View review pack'] ?? null)->toBe($expected) ->and($sharedEntry['targetUrl'] ?? null)->toBe($expected) ->and($sharedEntry['actionLabel'] ?? null)->toBe('View review pack') ->and($truth?->relatedArtifactUrl)->toBe($expected); }); it('keeps review-pack runs queued when the matching pack is still generating in Spec361', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $snapshot = seedEnvironmentReviewEvidence($tenant); $review = composeEnvironmentReviewForTest($tenant, $user, $snapshot); $options = [ 'include_pii' => true, 'include_operations' => false, ]; $fingerprint = app(ReviewPackService::class)->computeFingerprintForReview($review, $options); ReviewPack::factory()->generating()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'environment_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'initiated_by_user_id' => (int) $user->getKey(), 'fingerprint' => $fingerprint, 'options' => $options, ]); $run = OperationRun::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'user_id' => (int) $user->getKey(), 'initiator_name' => $user->name, 'type' => 'environment.review_pack.generate', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, 'created_at' => now()->subMinutes(25), 'context' => [ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'environment_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'include_pii' => $options['include_pii'], 'include_operations' => $options['include_operations'], ], ]); $change = app(AdapterRunReconciler::class)->reconcileOperationRun($run, true); expect($change['applied'] ?? null)->toBeFalse() ->and($change['decision'] ?? null)->toBe('not_reconciled') ->and((string) ($change['reason_message'] ?? ''))->toContain('still generating'); $run->refresh(); expect($run->status)->toBe(OperationRunStatus::Queued->value) ->and($run->outcome)->toBe(OperationRunOutcome::Pending->value); }); it('marks review-pack runs attention-required when the matching pack is expired in Spec361', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $snapshot = seedEnvironmentReviewEvidence($tenant); $review = composeEnvironmentReviewForTest($tenant, $user, $snapshot); $options = [ 'include_pii' => true, 'include_operations' => true, ]; $fingerprint = app(ReviewPackService::class)->computeFingerprintForReview($review, $options); ReviewPack::factory()->expired()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'environment_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'initiated_by_user_id' => (int) $user->getKey(), 'fingerprint' => $fingerprint, 'options' => $options, ]); $run = OperationRun::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'user_id' => (int) $user->getKey(), 'initiator_name' => $user->name, 'type' => 'environment.review_pack.generate', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, 'created_at' => now()->subMinutes(25), 'context' => [ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'environment_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'include_pii' => $options['include_pii'], 'include_operations' => $options['include_operations'], ], ]); $change = app(AdapterRunReconciler::class)->reconcileOperationRun($run, false); expect($change['applied'] ?? null)->toBeTrue(); $run->refresh(); expect($run->status)->toBe(OperationRunStatus::Completed->value) ->and($run->outcome)->toBe(OperationRunOutcome::Failed->value) ->and($run->reconciliationDecision())->toBe('attention_required') ->and((string) data_get($run->failure_summary, '0.message'))->toContain('already expired'); }); it('does not cross-scope reconcile review-pack runs in Spec361', function (): void { [$user, $tenant] = createUserWithTenant(role: 'owner'); $foreignTenant = ManagedEnvironment::factory()->create([ 'workspace_id' => (int) $tenant->workspace_id, ]); $snapshot = seedEnvironmentReviewEvidence($tenant); $review = composeEnvironmentReviewForTest($tenant, $user, $snapshot); $options = [ 'include_pii' => false, 'include_operations' => false, ]; $fingerprint = app(ReviewPackService::class)->computeFingerprintForReview($review, $options); ReviewPack::factory()->ready()->create([ 'managed_environment_id' => (int) $foreignTenant->getKey(), 'workspace_id' => (int) $foreignTenant->workspace_id, 'environment_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'initiated_by_user_id' => (int) $user->getKey(), 'fingerprint' => $fingerprint, 'options' => $options, ]); $run = OperationRun::factory()->create([ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'user_id' => (int) $user->getKey(), 'initiator_name' => $user->name, 'type' => 'environment.review_pack.generate', 'status' => OperationRunStatus::Queued->value, 'outcome' => OperationRunOutcome::Pending->value, 'created_at' => now()->subMinutes(25), 'context' => [ 'managed_environment_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'environment_review_id' => (int) $review->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'include_pii' => $options['include_pii'], 'include_operations' => $options['include_operations'], ], ]); $change = app(AdapterRunReconciler::class)->reconcileOperationRun($run, true); expect($change['applied'] ?? null)->toBeFalse() ->and($change['decision'] ?? null)->toBe('not_reconciled') ->and((string) ($change['reason_message'] ?? ''))->toContain('No matching review pack'); $run->refresh(); expect($run->status)->toBe(OperationRunStatus::Queued->value) ->and($run->outcome)->toBe(OperationRunOutcome::Pending->value) ->and($run->reconciledRelatedReviewPackId())->toBeNull(); });