environment(['local', 'testing'])) { $this->error('This fixture command is limited to local and testing environments.'); return self::FAILURE; } $fixture = config('tenantpilot.review_output.browser_smoke_fixture'); if (! is_array($fixture)) { $this->error('The review-output browser smoke fixture is not configured.'); return self::FAILURE; } $workspaceConfig = is_array($fixture['workspace'] ?? null) ? $fixture['workspace'] : []; $userConfig = is_array($fixture['user'] ?? null) ? $fixture['user'] : []; $scenarioConfig = is_array($fixture['ready_draft'] ?? null) ? $fixture['ready_draft'] : []; $workspace = Workspace::query()->updateOrCreate( ['slug' => (string) ($workspaceConfig['slug'] ?? 'spec-351-review-output-smoke')], ['name' => (string) ($workspaceConfig['name'] ?? 'Spec 351 Review Output Smoke')], ); $password = (string) ($userConfig['password'] ?? 'password'); $user = User::query()->updateOrCreate( ['email' => (string) ($userConfig['email'] ?? 'smoke-requester+351@tenantpilot.local')], [ 'name' => (string) ($userConfig['name'] ?? 'Spec 351 Requester'), 'password' => Hash::make($password), 'email_verified_at' => now(), ], ); $environment = ManagedEnvironment::query()->updateOrCreate( ['slug' => (string) ($scenarioConfig['managed_environment_slug'] ?? 'spec-351-browser-ready-draft')], [ 'workspace_id' => (int) $workspace->getKey(), 'name' => (string) ($scenarioConfig['managed_environment_name'] ?? 'Spec 351 Browser Ready Draft'), 'lifecycle_status' => ManagedEnvironment::STATUS_ACTIVE, 'kind' => 'dev', 'is_current' => false, 'metadata' => ['fixture' => 'spec-351-review-output-browser-smoke'], 'rbac_status' => 'ok', 'rbac_last_checked_at' => now(), ], ); WorkspaceMembership::query()->updateOrCreate( ['workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey()], ['role' => 'owner'], ); ManagedEnvironmentMembership::query()->updateOrCreate( ['managed_environment_id' => (int) $environment->getKey(), 'user_id' => (int) $user->getKey()], ['role' => 'owner', 'source' => 'manual', 'source_ref' => 'spec-351-review-output-browser-smoke'], ); if (Schema::hasColumn('users', 'last_workspace_id')) { $user->forceFill(['last_workspace_id' => (int) $workspace->getKey()])->save(); } if (Schema::hasTable('user_managed_environment_preferences')) { UserTenantPreference::query()->updateOrCreate( ['user_id' => (int) $user->getKey(), 'managed_environment_id' => (int) $environment->getKey()], ['last_used_at' => now()], ); } $readyReview = DB::transaction(function () use ($environment, $user, $scenarioConfig): EnvironmentReview { $this->resetFixtureGraph($environment); $snapshot = $this->seedReadyEvidenceSnapshot($environment, $user, $scenarioConfig); return $this->seedPublishedLoopWithReadySuccessor($environment, $user, $snapshot, $scenarioConfig); }); $workspaceUrl = CustomerReviewWorkspace::environmentFilterUrl($environment); $detailUrl = EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $readyReview], $environment); $detailRedirect = $this->relativeAdminRedirect($detailUrl); $publishedReviewId = EnvironmentReview::query() ->where('superseded_by_review_id', (int) $readyReview->getKey()) ->latest('id') ->value('id'); $loginUrl = route('admin.local.smoke-login', [ 'email' => (string) $user->email, 'tenant' => (string) $environment->slug, 'workspace' => (string) $workspace->slug, 'redirect' => $detailRedirect, ], absolute: false); $this->table( ['Fixture', 'Value'], [ ['Workspace', (string) $workspace->name], ['User email', (string) $user->email], ['User password', $password], ['ManagedEnvironment', (string) $environment->name], ['Workspace URL', $workspaceUrl], ['Published review id', $publishedReviewId !== null ? (string) $publishedReviewId : '—'], ['Ready review id', (string) $readyReview->getKey()], ['Ready review detail URL', $detailUrl], ['Fixture login URL', $loginUrl], ], ); $this->info('The fixture seeds a published review plus a linked ready successor so the workspace shows Open draft review and the detail page shows Publish review.'); return self::SUCCESS; } private function resetFixtureGraph(ManagedEnvironment $environment): void { $environmentId = (int) $environment->getKey(); $reviewIds = EnvironmentReview::query() ->where('managed_environment_id', $environmentId) ->pluck('id'); $snapshotIds = EvidenceSnapshot::query() ->where('managed_environment_id', $environmentId) ->pluck('id'); if ($reviewIds->isNotEmpty()) { ReviewPack::query()->whereIn('environment_review_id', $reviewIds)->delete(); EnvironmentReviewSection::query()->whereIn('environment_review_id', $reviewIds)->delete(); EnvironmentReview::query()->whereIn('id', $reviewIds)->delete(); } if ($snapshotIds->isNotEmpty()) { EvidenceSnapshotItem::query()->whereIn('evidence_snapshot_id', $snapshotIds)->delete(); EvidenceSnapshot::query()->whereIn('id', $snapshotIds)->delete(); } StoredReport::query()->where('managed_environment_id', $environmentId)->delete(); } /** * @param array $scenarioConfig */ private function seedReadyEvidenceSnapshot( ManagedEnvironment $environment, User $user, array $scenarioConfig, ): EvidenceSnapshot { StoredReport::query()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'report_type' => StoredReport::REPORT_TYPE_PERMISSION_POSTURE, 'payload' => [ 'posture_score' => 92, 'required_count' => 14, 'granted_count' => 14, 'checked_at' => now()->subMinutes(5)->toIso8601String(), 'permissions' => [ ['key' => 'DeviceManagementConfiguration.ReadWrite.All', 'status' => 'granted'], ], ], ]); StoredReport::query()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'report_type' => StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES, 'payload' => [ 'provider_key' => 'microsoft', 'domain' => 'entra.admin_roles', 'measured_at' => now()->subMinutes(5)->toIso8601String(), 'roles' => [ [ 'displayName' => 'Global Administrator', 'userPrincipalName' => 'admin@contoso.com', ], ], ], ]); $operationRun = OperationRun::query()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'user_id' => (int) $user->getKey(), 'initiator_name' => (string) ($scenarioConfig['operation_initiator_name'] ?? 'Spec 351 Browser Operator'), 'type' => OperationRunType::PolicySync->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, 'run_identity_hash' => hash('sha256', sprintf( 'spec-351-review-output-browser-smoke-%s-%s', $environment->getKey(), (string) Str::uuid(), )), 'summary_counts' => [], 'failure_summary' => [], 'context' => ['fixture' => 'spec-351-review-output-browser-smoke'], 'started_at' => now()->subMinutes(6), 'completed_at' => now()->subMinutes(5), ]); /** @var EvidenceSnapshotService $service */ $service = app(EvidenceSnapshotService::class); $payload = $service->buildSnapshotPayload($environment); $snapshot = EvidenceSnapshot::query()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'operation_run_id' => (int) $operationRun->getKey(), 'initiated_by_user_id' => (int) $user->getKey(), 'status' => EvidenceSnapshotStatus::Active->value, 'fingerprint' => $payload['fingerprint'], 'completeness_state' => $payload['completeness'], 'summary' => $payload['summary'], 'generated_at' => now()->subMinutes(4), ]); foreach ($payload['items'] as $item) { $snapshot->items()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'dimension_key' => $item['dimension_key'], 'state' => $item['state'], 'required' => $item['required'], 'source_kind' => $item['source_kind'], 'source_record_type' => $item['source_record_type'], 'source_record_id' => $item['source_record_id'], 'source_fingerprint' => $item['source_fingerprint'], 'measured_at' => $item['measured_at'], 'freshness_at' => $item['freshness_at'], 'summary_payload' => $item['summary_payload'], 'sort_order' => $item['sort_order'], ]); } return $snapshot->load('items'); } /** * @param array $scenarioConfig */ private function seedPublishedLoopWithReadySuccessor( ManagedEnvironment $environment, User $user, EvidenceSnapshot $snapshot, array $scenarioConfig, ): EnvironmentReview { /** @var EnvironmentReviewService $service */ $service = app(EnvironmentReviewService::class); /** @var EnvironmentReviewLifecycleService $lifecycle */ $lifecycle = app(EnvironmentReviewLifecycleService::class); Queue::fake(); $publishedReview = $service->create($environment, $snapshot, $user)->refresh(); if ($publishedReview->generated_at === null || ! $publishedReview->sections()->exists()) { $publishedReview = $service->compose($publishedReview); } $publishedReview = $this->markReadyReview($publishedReview); $publishedReview->forceFill([ 'status' => EnvironmentReviewStatus::Ready->value, 'published_at' => null, 'published_by_user_id' => null, ])->save(); $publishedReview = $lifecycle->publish( $publishedReview->refresh(), $user, (string) ($scenarioConfig['publish_reason'] ?? 'Seed published predecessor for Spec 351 browser verification.'), ); $this->attachPublishedReviewPack($environment, $user, $publishedReview, $snapshot, $scenarioConfig); $successorReview = $lifecycle->createNextReview($publishedReview->fresh(), $user, $snapshot)->refresh(); if ($successorReview->generated_at === null || ! $successorReview->sections()->exists()) { $successorReview = $service->compose($successorReview); } $successorReview = $this->markReadyReview($successorReview); $successorReview->forceFill([ 'status' => EnvironmentReviewStatus::Ready->value, 'published_at' => null, 'published_by_user_id' => null, ])->save(); return $successorReview->refresh()->load(['tenant', 'evidenceSnapshot.items', 'sections']); } /** * @param array $scenarioConfig */ private function attachPublishedReviewPack( ManagedEnvironment $environment, User $user, EnvironmentReview $review, EvidenceSnapshot $snapshot, array $scenarioConfig, ): void { $filePath = (string) ($scenarioConfig['published_review_pack_path'] ?? 'review-packs/spec351-browser-ready-published.zip'); $fileContents = (string) ($scenarioConfig['published_review_pack_contents'] ?? 'PK-spec351-browser-ready-published'); Storage::disk('exports')->put($filePath, $fileContents); $fingerprint = hash('sha256', implode('|', [ 'spec-351-review-output-browser-smoke-pack', (string) $environment->getKey(), (string) $review->getKey(), $filePath, ])); $reviewPack = ReviewPack::query()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'initiated_by_user_id' => (int) $user->getKey(), 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'environment_review_id' => (int) $review->getKey(), 'status' => ReviewPack::STATUS_READY, 'fingerprint' => $fingerprint, 'summary' => [ 'fixture' => 'spec-351-review-output-browser-smoke', ], 'options' => [ 'include_pii' => false, 'include_operations' => true, ], 'file_disk' => 'exports', 'file_path' => $filePath, 'file_size' => strlen($fileContents), 'sha256' => hash('sha256', $fileContents), 'generated_at' => now()->subMinutes(3), 'expires_at' => now()->addDays(7), ]); $summary = is_array($review->summary) ? $review->summary : []; $summary['has_ready_export'] = true; $review->forceFill([ 'current_export_review_pack_id' => (int) $reviewPack->getKey(), 'summary' => $summary, ])->save(); } private function markReadyReview(EnvironmentReview $review): EnvironmentReview { $review->loadMissing(['sections', 'evidenceSnapshot.items']); $disclosure = 'TenantPilot interprets available evidence for review readiness. This is not a certification, legal attestation, or compliance guarantee.'; $controlSummary = [ 'control_key' => 'customer-output', 'control_name' => 'Customer output', 'domain_key' => 'customer_delivery', 'readiness_bucket' => 'evidence_on_record', 'readiness_label' => 'Evidence on record', 'limitation_flags' => [], 'limitation_labels' => [], 'customer_summary' => 'Customer output has evidence on record in this released review.', 'evidence_basis_summary' => '1 evidence signal references this control.', 'accepted_risk_summary' => null, 'recommended_next_action' => 'Publish review.', 'detail_anchor' => 'control-customer-output', 'supporting_finding_ids' => [], 'finding_count' => 0, 'open_finding_count' => 0, 'accepted_risk_count' => 0, ]; $controlExplanation = [ 'title' => 'Customer output', 'control_key' => 'customer-output', 'control_name' => 'Customer output', 'readiness_bucket' => 'evidence_on_record', 'readiness_label' => 'Evidence on record', 'limitation_flags' => [], 'limitation_labels' => [], 'customer_summary' => 'Customer output has evidence on record in this released review.', 'evidence_basis_summary' => '1 evidence signal references this control.', 'accepted_risk_summary' => null, 'explanation_text' => 'Customer output has evidence on record in this released review.', 'evidence_basis_items' => [ '1 evidence signal references this control.', ], 'accepted_risk_context' => null, 'recommended_next_action' => 'Publish review.', 'proof_access_state' => 'available', 'supporting_finding_ids' => [], ]; $snapshot = $review->evidenceSnapshot; if ($snapshot instanceof EvidenceSnapshot) { $snapshot->items->each(function (EvidenceSnapshotItem $item): void { $item->forceFill([ 'state' => EvidenceCompletenessState::Complete->value, ])->save(); }); $snapshotSummary = is_array($snapshot->summary) ? $snapshot->summary : []; $snapshotSummary['missing_dimensions'] = 0; $snapshotSummary['stale_dimensions'] = 0; $snapshotSummary['dimensions'] = collect($snapshotSummary['dimensions'] ?? []) ->map(static function (mixed $dimension): mixed { if (! is_array($dimension)) { return $dimension; } $dimension['state'] = EnvironmentReviewCompletenessState::Complete->value; return $dimension; }) ->values() ->all(); $snapshot->forceFill([ 'completeness_state' => EvidenceCompletenessState::Complete->value, 'summary' => $snapshotSummary, ])->save(); } $review->sections->each(function (EnvironmentReviewSection $section) use ($controlSummary, $controlExplanation, $disclosure): void { $attributes = [ 'completeness_state' => EnvironmentReviewCompletenessState::Complete->value, ]; if ($section->isControlInterpretation()) { $attributes['summary_payload'] = array_replace_recursive([ 'version_key' => ComplianceEvidenceMappingV1::VERSION_KEY, 'display_label' => 'Compliance evidence mapping v1', 'non_certification_disclosure' => $disclosure, 'mapped_control_count' => 1, 'follow_up_required_count' => 0, 'limitation_counts' => [], 'limitations' => [], ], is_array($section->summary_payload) ? $section->summary_payload : []); $attributes['render_payload'] = array_replace_recursive([ 'entries' => [$controlExplanation], 'disclosure' => $disclosure, 'next_actions' => ['Publish review.'], 'empty_state' => null, ], is_array($section->render_payload) ? $section->render_payload : []); } $section->forceFill($attributes)->save(); }); $sectionCount = $review->sections->count(); $summary = is_array($review->summary) ? $review->summary : []; $existingControlInterpretation = is_array($summary['control_interpretation'] ?? null) ? $summary['control_interpretation'] : []; $existingControls = collect($existingControlInterpretation['controls'] ?? []) ->filter(static fn (mixed $control): bool => is_array($control)) ->values(); $summary['control_interpretation'] = array_replace_recursive([ 'version_key' => ComplianceEvidenceMappingV1::VERSION_KEY, 'display_label' => 'Compliance evidence mapping v1', 'non_certification_disclosure' => $disclosure, 'mapped_control_count' => 1, 'follow_up_required_count' => 0, 'limitation_counts' => [], 'limitations' => [], 'controls' => [$controlSummary], ], $existingControlInterpretation); $summary['control_interpretation']['controls'] = [ array_replace( $controlSummary, is_array($existingControls->first()) ? $existingControls->first() : [], ), ]; $summary['publish_blockers'] = []; $summary['section_count'] = $sectionCount; $summary['section_state_counts'] = [ 'complete' => $sectionCount, 'partial' => 0, 'missing' => 0, 'stale' => 0, ]; $review->forceFill([ 'completeness_state' => EnvironmentReviewCompletenessState::Complete->value, 'summary' => $summary, ])->save(); return $review->refresh(); } private function relativeAdminRedirect(string $url): string { $parsed = parse_url($url); $path = '/'.ltrim((string) ($parsed['path'] ?? ''), '/'); $query = isset($parsed['query']) ? '?'.$parsed['query'] : ''; $fragment = isset($parsed['fragment']) ? '#'.$parsed['fragment'] : ''; return $path.$query.$fragment; } }