feat: align evidence review pack product process flow (Spec 337) (#407)

## Summary

Productizes the Evidence Overview review-pack process flow so the operator sees a clear, gated progression:

`evidence snapshot → stored report → review pack → customer-safe export`

with explicit gating, state-appropriate copy, collapsed diagnostics, and dark-mode coverage.

## Changes

- `EvidenceOverview` page + Blade view aligned to the review-pack state contract.
- New feature test: `Spec337EvidenceReviewPackProductFlowTest`.
- New browser smoke: `Spec337EvidenceReviewPackProductFlowSmokeTest`.
- Spec 337 artifacts: `spec.md`, `plan.md`, `tasks.md`, state contract, repo-truth map, checklist, and screenshot evidence.

## Spec Kit

Spec + code in one PR (Variante B). Gate satisfied: includes `specs/337-evidence-review-pack-product-process-flow-alignment/`.

## Notes

Filament v5 / Livewire v4 compliant. No destructive actions added. Tooling scratch (`.playwright-mcp/`) intentionally excluded from the commit.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #407
This commit is contained in:
ahmido 2026-05-30 13:41:19 +00:00
parent 4c661f18f0
commit b7c0dfe0e3
20 changed files with 3422 additions and 91 deletions

View File

@ -5,6 +5,7 @@
namespace App\Filament\Pages\Monitoring;
use App\Filament\Concerns\ClearsWorkspaceHubEnvironmentFilterState;
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
use App\Filament\Resources\EvidenceSnapshotResource;
use App\Filament\Resources\ReviewPackResource;
use App\Filament\Resources\StoredReportResource;
@ -16,12 +17,19 @@
use App\Models\StoredReport;
use App\Models\User;
use App\Models\Workspace;
use App\Services\ReviewPackService;
use App\Support\Auth\Capabilities;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\EnvironmentReviewStatus;
use App\Support\Evidence\EvidenceCompletenessState;
use App\Support\Evidence\EvidenceSnapshotStatus;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Navigation\WorkspaceHubEnvironmentFilter;
use App\Support\OperationRunLinks;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\ReviewPackStatus;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
@ -290,17 +298,48 @@ public function environmentFilterChip(): ?array
public function evidenceDisclosurePayload(): array
{
$snapshots = $this->scopedSnapshots();
$primarySnapshot = $snapshots->first();
$primaryTenant = $primarySnapshot?->tenant;
$filteredTenant = $this->filteredTenant();
$primarySnapshot = $this->primarySnapshotForScope($snapshots, $filteredTenant);
$primaryTenant = $filteredTenant instanceof ManagedEnvironment
? $filteredTenant
: $primarySnapshot?->tenant;
$primaryReviewPack = $primarySnapshot instanceof EvidenceSnapshot
? $this->latestReviewPackForSnapshot($primarySnapshot)
: null;
$primaryStoredReport = $primaryTenant instanceof ManagedEnvironment
? $this->latestStoredReportForTenant($primaryTenant)
: null;
$primaryReview = $primarySnapshot instanceof EvidenceSnapshot
? $this->latestEnvironmentReviewForSnapshot($primarySnapshot)
: null;
$primaryOperationRun = $this->primaryOperationRun($primarySnapshot, $primaryReviewPack);
$primaryAction = $this->primaryEvidenceAction($primarySnapshot, $primaryReviewPack, $primaryStoredReport, $primaryOperationRun);
$primaryState = $this->primaryProofState($primarySnapshot, $primaryReviewPack, $primaryStoredReport, $primaryOperationRun);
$decisionState = $this->evidenceReviewPackDecisionState(
$primarySnapshot,
$primaryReviewPack,
$primaryStoredReport,
$primaryReview,
$primaryTenant,
$primaryOperationRun,
);
$primaryAction = $this->primaryEvidenceAction(
$decisionState,
$primarySnapshot,
$primaryReviewPack,
$primaryStoredReport,
$primaryReview,
$primaryTenant,
$primaryOperationRun,
);
$decisionCard = $this->evidenceReviewPackDecisionCard(
$decisionState,
$primarySnapshot,
$primaryReviewPack,
$primaryStoredReport,
$primaryReview,
$primaryTenant,
$primaryOperationRun,
$primaryAction,
);
return [
'scope_label' => $this->evidenceScopeLabel(),
@ -308,12 +347,37 @@ public function evidenceDisclosurePayload(): array
'snapshot_count' => $snapshots->count(),
'primary_title' => $primaryTenant instanceof ManagedEnvironment
? $primaryTenant->name
: 'No evidence for this scope',
: 'Select an environment to evaluate evidence readiness',
'primary_summary' => $primarySnapshot instanceof EvidenceSnapshot
? $this->productSafeEvidenceReason($this->snapshotOutcome($primarySnapshot)->primaryReason)
: 'No evidence snapshot has been generated for the active workspace scope.',
'primary_state' => $primaryState,
: ($primaryTenant instanceof ManagedEnvironment
? 'No evidence snapshot has been generated for the active workspace scope.'
: 'Choose an authorized environment from the scope control before building a customer-safe proof path.'),
'primary_proof_state' => $this->primaryProofState(
$primarySnapshot,
$primaryReviewPack,
$primaryStoredReport,
$primaryOperationRun,
),
'decision_card' => $decisionCard,
'readiness_flow' => $this->evidenceReviewPackReadinessFlow(
$decisionState,
$primarySnapshot,
$primaryReviewPack,
$primaryStoredReport,
$primaryReview,
$primaryTenant,
),
'primary_action' => $primaryAction,
'review_pack_coverage' => $this->reviewPackCoverageSummary($primaryReviewPack),
'proof_items' => $this->evidenceReviewPackProofItems(
$primarySnapshot,
$primaryReviewPack,
$primaryStoredReport,
$primaryReview,
$primaryTenant,
$primaryOperationRun,
),
'cards' => [
$this->snapshotProofCard($primarySnapshot),
$this->reviewPackProofCard($primaryReviewPack, $primarySnapshot),
@ -329,6 +393,668 @@ public function evidenceDisclosurePayload(): array
];
}
/**
* @param Collection<int, EvidenceSnapshot> $snapshots
*/
private function primarySnapshotForScope(Collection $snapshots, ?ManagedEnvironment $filteredTenant): ?EvidenceSnapshot
{
if ($filteredTenant instanceof ManagedEnvironment) {
return $this->latestEvidenceSnapshotForTenant($filteredTenant) ?? $snapshots->first();
}
return $snapshots->first();
}
private function latestEvidenceSnapshotForTenant(ManagedEnvironment $tenant): ?EvidenceSnapshot
{
return EvidenceSnapshot::query()
->with([
'tenant',
'operationRun',
'reviewPacks.operationRun',
'reviewPacks.environmentReview.currentExportReviewPack',
'items',
])
->where('workspace_id', (int) $tenant->workspace_id)
->where('managed_environment_id', (int) $tenant->getKey())
->whereIn('status', [
EvidenceSnapshotStatus::Queued->value,
EvidenceSnapshotStatus::Generating->value,
EvidenceSnapshotStatus::Active->value,
EvidenceSnapshotStatus::Failed->value,
EvidenceSnapshotStatus::Expired->value,
])
->orderByRaw('COALESCE(generated_at, created_at) DESC')
->first();
}
private function latestEnvironmentReviewForSnapshot(EvidenceSnapshot $snapshot): ?EnvironmentReview
{
return EnvironmentReview::query()
->with(['currentExportReviewPack.operationRun', 'operationRun'])
->where('workspace_id', (int) $snapshot->workspace_id)
->where('managed_environment_id', (int) $snapshot->managed_environment_id)
->where('evidence_snapshot_id', (int) $snapshot->getKey())
->orderByRaw('COALESCE(generated_at, created_at) DESC')
->first();
}
private function evidenceReviewPackDecisionState(
?EvidenceSnapshot $snapshot,
?ReviewPack $reviewPack,
?StoredReport $storedReport,
?EnvironmentReview $review,
?ManagedEnvironment $tenant,
?OperationRun $operationRun,
): string {
if (! $tenant instanceof ManagedEnvironment && ! $snapshot instanceof EvidenceSnapshot) {
return 'source_unavailable';
}
if (! $snapshot instanceof EvidenceSnapshot) {
return 'no_snapshot';
}
$snapshotStatus = (string) $snapshot->status;
if (in_array($snapshotStatus, [EvidenceSnapshotStatus::Queued->value, EvidenceSnapshotStatus::Generating->value], true)) {
return 'snapshot_generating';
}
if ($snapshotStatus === EvidenceSnapshotStatus::Failed->value) {
return 'snapshot_failed';
}
if ($this->snapshotIsStale($snapshot)) {
return 'snapshot_stale';
}
if ($reviewPack instanceof ReviewPack) {
$packStatus = (string) $reviewPack->status;
if (in_array($packStatus, [ReviewPackStatus::Queued->value, ReviewPackStatus::Generating->value], true)) {
return 'pack_generating';
}
if ($packStatus === ReviewPackStatus::Failed->value) {
return 'pack_failed';
}
}
if (! $storedReport instanceof StoredReport) {
return 'report_missing';
}
if (! $reviewPack instanceof ReviewPack) {
return 'pack_required';
}
if (! $this->reviewPackHasExportArtifact($reviewPack)) {
return 'export_unavailable';
}
if ($this->customerSafeOutputReady($review, $reviewPack)) {
return $this->canDownloadReviewPack($reviewPack, $tenant) ? 'export_available' : 'customer_safe_ready';
}
return $operationRun instanceof OperationRun && (string) $operationRun->outcome === OperationRunOutcome::Failed->value
? 'pack_failed'
: 'customer_review_required';
}
/**
* @param array{label:string,url:string}|null $primaryAction
* @return array<string, mixed>
*/
private function evidenceReviewPackDecisionCard(
string $state,
?EvidenceSnapshot $snapshot,
?ReviewPack $reviewPack,
?StoredReport $storedReport,
?EnvironmentReview $review,
?ManagedEnvironment $tenant,
?OperationRun $operationRun,
?array $primaryAction,
): array {
$base = match ($state) {
'no_snapshot' => [
'status' => 'Evidence snapshot required',
'tone' => 'warning',
'reason' => 'No evidence snapshot is available for the selected review scope.',
'impact' => 'Review pack output cannot be trusted or exported yet.',
],
'snapshot_generating' => [
'status' => 'Evidence generation in progress',
'tone' => 'info',
'reason' => 'Evidence snapshot generation is currently running.',
'impact' => 'Review pack output is not final yet.',
],
'snapshot_failed' => [
'status' => 'Evidence generation failed',
'tone' => 'danger',
'reason' => 'Evidence snapshot generation ended with errors.',
'impact' => 'Review pack output cannot be generated from this evidence yet.',
],
'snapshot_stale' => [
'status' => 'Evidence refresh required',
'tone' => 'warning',
'reason' => 'Evidence exists, but its freshness is outside the acceptable window or the snapshot is expired or stale.',
'impact' => 'Review pack output should not be treated as current until evidence is refreshed.',
],
'report_missing' => [
'status' => 'Stored report required',
'tone' => 'warning',
'reason' => 'Evidence snapshot exists, but no stored report is available for this review output.',
'impact' => 'Evidence is present but not yet packaged for consumption.',
],
'pack_required' => [
'status' => 'Review pack required',
'tone' => 'warning',
'reason' => 'Stored report exists, but a review pack has not been generated.',
'impact' => 'Customer-safe delivery is not ready yet.',
],
'pack_generating' => [
'status' => 'Review pack generation in progress',
'tone' => 'info',
'reason' => 'Review pack generation is currently running.',
'impact' => 'Customer output is not final yet.',
],
'pack_failed' => [
'status' => 'Review pack generation failed',
'tone' => 'danger',
'reason' => 'Review pack generation ended with errors.',
'impact' => 'Customer-safe output cannot be generated from this pack yet.',
],
'customer_review_required' => [
'status' => 'Customer-safe review required',
'tone' => 'warning',
'reason' => 'A review pack exists, but customer-safe output has not been confirmed by repo-backed review/package readiness.',
'impact' => 'Do not share the pack externally until it has been reviewed.',
],
'customer_safe_ready' => [
'status' => 'Customer-safe output ready',
'tone' => 'success',
'reason' => 'Review pack output is backed by a published review and its current export pack.',
'impact' => 'Authorized users can use the pack; external sharing remains governed by workspace policy.',
],
'export_available' => [
'status' => 'Review pack export available',
'tone' => 'success',
'reason' => 'A generated export artifact is available for authorized download.',
'impact' => 'Download is available from this surface; external sharing remains governed by workspace policy.',
],
'export_unavailable' => [
'status' => 'Export unavailable',
'tone' => 'warning',
'reason' => 'No generated export artifact is available, the pack is not ready, the file is missing or expired, or external delivery is not configured.',
'impact' => 'Evidence package cannot be downloaded or delivered from this surface yet.',
],
default => [
'status' => 'Evidence source unavailable',
'tone' => 'gray',
'reason' => 'No repo-backed source data is selected for this proof scope.',
'impact' => 'No customer or auditor consumption decision should rely on this surface yet.',
],
};
return $base + [
'question' => 'Is this evidence package ready for customer or auditor consumption?',
'statusLabel' => 'Status',
'reasonLabel' => 'Reason',
'impactLabel' => 'Impact',
'nextActionLabel' => 'Primary next action',
'evidenceLabel' => 'Evidence path',
'evidence' => $this->decisionEvidenceSummary($snapshot, $reviewPack, $storedReport, $review, $tenant, $operationRun),
'actionLabel' => $primaryAction['label'] ?? ($state === 'source_unavailable' ? 'Select environment scope' : 'No authorized action available'),
'actionUrl' => $primaryAction['url'] ?? null,
'helperText' => $this->decisionActionHelper($state, $tenant, $primaryAction),
'actionDescription' => $this->decisionActionDescription($state, $primaryAction),
];
}
/**
* @return list<array<string, mixed>>
*/
private function evidenceReviewPackReadinessFlow(
string $state,
?EvidenceSnapshot $snapshot,
?ReviewPack $reviewPack,
?StoredReport $storedReport,
?EnvironmentReview $review,
?ManagedEnvironment $tenant,
): array {
$sourceState = $tenant instanceof ManagedEnvironment ? 'Available' : 'Unavailable';
$snapshotState = $this->snapshotFlowState($snapshot);
$storedReportState = $this->storedReportFlowState($snapshot, $storedReport);
$reviewPackState = $this->reviewPackFlowState($snapshot, $storedReport, $reviewPack);
$customerSafeState = $this->customerSafeFlowState($snapshot, $reviewPack, $review);
$exportState = $this->exportFlowState($snapshot, $reviewPack, $tenant);
return [
$this->flowStep('Source data selected', $sourceState, $sourceState === 'Available' ? 'Environment scope is established from the workspace context.' : 'Select an authorized environment before building customer-safe evidence.', $this->flowTone($sourceState), $state === 'source_unavailable'),
$this->flowStep('Evidence snapshot', $snapshotState, $this->snapshotFlowDescription($snapshotState), $this->flowTone($snapshotState), in_array($state, ['no_snapshot', 'snapshot_generating', 'snapshot_failed', 'snapshot_stale'], true)),
$this->flowStep('Stored report', $storedReportState, $this->storedReportFlowDescription($storedReportState), $this->flowTone($storedReportState), $state === 'report_missing'),
$this->flowStep('Review pack', $reviewPackState, $this->reviewPackFlowDescription($reviewPackState), $this->flowTone($reviewPackState), in_array($state, ['pack_required', 'pack_generating', 'pack_failed'], true)),
$this->flowStep('Customer-safe output', $customerSafeState, $this->customerSafeFlowDescription($customerSafeState), $this->flowTone($customerSafeState), $state === 'customer_review_required'),
$this->flowStep('Export / delivery', $exportState, $this->exportFlowDescription($exportState), $this->flowTone($exportState), in_array($state, ['export_available', 'export_unavailable'], true)),
];
}
/**
* @return array{description:string,items:list<array{label:string,value:string}>}
*/
private function reviewPackCoverageSummary(?ReviewPack $reviewPack): array
{
if (! $reviewPack instanceof ReviewPack) {
return [
'description' => 'Review pack coverage details are not available for this artifact.',
'items' => [],
];
}
$summary = is_array($reviewPack->summary) ? $reviewPack->summary : [];
$items = [];
foreach ([
'finding_count' => 'Findings included',
'report_count' => 'Reports included',
'operation_count' => 'Operations included',
'section_count' => 'Review sections',
] as $key => $label) {
if (array_key_exists($key, $summary) && is_numeric($summary[$key])) {
$items[] = ['label' => $label, 'value' => (string) ((int) $summary[$key])];
}
}
$requiredDimensions = data_get($summary, 'evidence_resolution.required_dimensions');
if (is_array($requiredDimensions)) {
$items[] = ['label' => 'Evidence dimensions', 'value' => (string) count($requiredDimensions)];
}
if (filled($reviewPack->file_path)) {
$items[] = ['label' => 'Generated files', 'value' => '1'];
}
return [
'description' => $items === []
? 'Review pack coverage details are not available for this artifact.'
: 'Coverage values are derived from the generated review-pack summary and file metadata.',
'items' => $items,
];
}
/**
* @return list<array<string, mixed>>
*/
private function evidenceReviewPackProofItems(
?EvidenceSnapshot $snapshot,
?ReviewPack $reviewPack,
?StoredReport $storedReport,
?EnvironmentReview $review,
?ManagedEnvironment $tenant,
?OperationRun $operationRun,
): array {
$operationDescription = $operationRun instanceof OperationRun
? sprintf(
'%s · %s · Started %s · Completed %s · Requested by %s',
(string) $operationRun->type,
Str::headline((string) $operationRun->outcome),
$operationRun->started_at?->toDateTimeString() ?? '—',
$operationRun->completed_at?->toDateTimeString() ?? '—',
(string) ($operationRun->initiator_name ?: 'Unknown'),
)
: 'Operation proof unavailable. No generation operation is linked to this artifact.';
return [
$this->proofItem('Source data', $tenant instanceof ManagedEnvironment ? 'Available' : 'Unavailable', $tenant instanceof ManagedEnvironment ? 'Workspace and environment scope are established.' : 'No environment scope is selected.', $tenant instanceof ManagedEnvironment ? 'success' : 'gray'),
$this->proofItemFromCard($this->snapshotProofCard($snapshot)),
$this->proofItemFromCard($this->storedReportProofCard($storedReport, $tenant)),
$this->proofItemFromCard($this->reviewPackProofCard($reviewPack, $snapshot)),
$this->proofItem('Operation proof', $operationRun instanceof OperationRun ? $this->operationProofState($operationRun) : 'Unavailable', $operationDescription, $operationRun instanceof OperationRun ? $this->operationProofTone($operationRun) : 'gray', $operationRun instanceof OperationRun ? OperationRunLinks::tenantlessView($operationRun) : null, $operationRun instanceof OperationRun ? OperationRunLinks::openLabel() : null),
$this->proofItem('Export artifact', $this->exportFlowState($snapshot, $reviewPack, $tenant), $this->exportProofDescription($reviewPack, $tenant), $this->flowTone($this->exportFlowState($snapshot, $reviewPack, $tenant)), $this->canDownloadReviewPack($reviewPack, $tenant) ? app(ReviewPackService::class)->generateDownloadUrl($reviewPack) : null, $this->canDownloadReviewPack($reviewPack, $tenant) ? 'Download export' : null),
$this->proofItem('Customer-safe state', $this->customerSafeFlowState($snapshot, $reviewPack, $review), $this->customerSafeFlowDescription($this->customerSafeFlowState($snapshot, $reviewPack, $review)), $this->flowTone($this->customerSafeFlowState($snapshot, $reviewPack, $review)), $tenant instanceof ManagedEnvironment ? CustomerReviewWorkspace::environmentFilterUrl($tenant) : null, $tenant instanceof ManagedEnvironment ? 'Open customer workspace' : null),
$this->proofItem('Diagnostics', 'Collapsed', 'Raw report metadata, raw evidence payloads, generation diagnostics, export diagnostics, provider diagnostics, stack traces, and internal exceptions stay collapsed by default.', 'gray'),
];
}
/**
* @return array{label:string,state:string,description:string,tone:string,currentBlocker:bool}
*/
private function flowStep(string $label, string $state, string $description, string $tone, bool $currentBlocker = false): array
{
return [
'label' => $label,
'state' => $state,
'description' => $description,
'tone' => $tone,
'currentBlocker' => $currentBlocker,
];
}
private function snapshotFlowState(?EvidenceSnapshot $snapshot): string
{
if (! $snapshot instanceof EvidenceSnapshot) {
return 'Missing';
}
return match ((string) $snapshot->status) {
EvidenceSnapshotStatus::Queued->value, EvidenceSnapshotStatus::Generating->value => 'Generating',
EvidenceSnapshotStatus::Failed->value => 'Failed',
default => $this->snapshotIsStale($snapshot) ? 'Stale' : 'Available',
};
}
private function storedReportFlowState(?EvidenceSnapshot $snapshot, ?StoredReport $storedReport): string
{
if (! $snapshot instanceof EvidenceSnapshot || in_array($this->snapshotFlowState($snapshot), ['Generating', 'Failed'], true)) {
return 'Unavailable';
}
return $storedReport instanceof StoredReport ? 'Available' : 'Missing';
}
private function reviewPackFlowState(?EvidenceSnapshot $snapshot, ?StoredReport $storedReport, ?ReviewPack $reviewPack): string
{
if ($reviewPack instanceof ReviewPack) {
return match ((string) $reviewPack->status) {
ReviewPackStatus::Queued->value, ReviewPackStatus::Generating->value => 'Generating',
ReviewPackStatus::Failed->value => 'Failed',
ReviewPackStatus::Ready->value => 'Available',
default => 'Unavailable',
};
}
if (! $snapshot instanceof EvidenceSnapshot) {
return 'Unavailable';
}
return $storedReport instanceof StoredReport ? 'Required' : 'Unavailable';
}
private function customerSafeFlowState(?EvidenceSnapshot $snapshot, ?ReviewPack $reviewPack, ?EnvironmentReview $review): string
{
if (! $snapshot instanceof EvidenceSnapshot) {
return 'Not ready';
}
if (! $reviewPack instanceof ReviewPack || ! $reviewPack->isReady()) {
return 'Not ready';
}
return $this->customerSafeOutputReady($review, $reviewPack) ? 'Ready' : 'Needs review';
}
private function exportFlowState(?EvidenceSnapshot $snapshot, ?ReviewPack $reviewPack, ?ManagedEnvironment $tenant): string
{
if (! $snapshot instanceof EvidenceSnapshot || ! $reviewPack instanceof ReviewPack) {
return 'Unavailable';
}
if ((string) $reviewPack->status === ReviewPackStatus::Failed->value) {
return 'Failed';
}
if (! $reviewPack->isReady()) {
return 'Unavailable';
}
return $this->reviewPackHasExportArtifact($reviewPack) && $this->canDownloadReviewPack($reviewPack, $tenant)
? 'Available'
: 'Required';
}
private function snapshotFlowDescription(string $state): string
{
return match ($state) {
'Available' => 'Snapshot proof exists.',
'Missing' => 'No snapshot in scope.',
'Generating' => 'Generation is running.',
'Failed' => 'Generation failed.',
'Stale' => 'Evidence is stale or expired.',
default => 'Evidence snapshot state is unavailable.',
};
}
private function storedReportFlowDescription(string $state): string
{
return match ($state) {
'Available' => 'Stored report exists.',
'Missing' => 'No report for this output.',
default => 'Depends on snapshot availability.',
};
}
private function reviewPackFlowDescription(string $state): string
{
return match ($state) {
'Available' => 'Review pack exists.',
'Required' => 'Generate a review pack.',
'Generating' => 'Generation is running.',
'Failed' => 'Generation failed.',
default => 'Blocked by earlier proof.',
};
}
private function customerSafeFlowDescription(string $state): string
{
return match ($state) {
'Ready' => 'Published review backs current export pack.',
'Needs review' => 'Readiness is not confirmed.',
'Not ready' => 'Customer-safe output is not ready.',
default => 'Customer-safe output readiness is unavailable.',
};
}
private function exportFlowDescription(string $state): string
{
return match ($state) {
'Available' => 'Authorized download is available.',
'Required' => 'Export file is missing or unauthorized.',
'Failed' => 'The linked review-pack export failed.',
default => 'No generated export is available.',
};
}
private function flowTone(string $state): string
{
return match ($state) {
'Available', 'Ready', 'Generated' => 'success',
'Generating' => 'info',
'Missing', 'Required', 'Stale', 'Needs review', 'Not ready' => 'warning',
'Failed' => 'danger',
default => 'gray',
};
}
private function snapshotIsStale(EvidenceSnapshot $snapshot): bool
{
if ((string) $snapshot->status === EvidenceSnapshotStatus::Expired->value) {
return true;
}
if ($snapshot->expires_at instanceof \DateTimeInterface && $snapshot->expires_at->isPast()) {
return true;
}
return $snapshot->completenessState() === EvidenceCompletenessState::Stale
|| (int) data_get($snapshot->summary ?? [], 'stale_dimensions', 0) > 0;
}
private function reviewPackHasExportArtifact(?ReviewPack $reviewPack): bool
{
if (! $reviewPack instanceof ReviewPack || ! $reviewPack->isReady()) {
return false;
}
if ($reviewPack->expires_at instanceof \DateTimeInterface && $reviewPack->expires_at->isPast()) {
return false;
}
return filled($reviewPack->file_disk) && filled($reviewPack->file_path);
}
private function customerSafeOutputReady(?EnvironmentReview $review, ?ReviewPack $reviewPack): bool
{
if (! $review instanceof EnvironmentReview || ! $reviewPack instanceof ReviewPack) {
return false;
}
return (string) $review->status === EnvironmentReviewStatus::Published->value
&& (int) $review->current_export_review_pack_id === (int) $reviewPack->getKey()
&& $this->reviewPackHasExportArtifact($reviewPack);
}
private function canDownloadReviewPack(?ReviewPack $reviewPack, ?ManagedEnvironment $tenant): bool
{
$user = auth()->user();
return $reviewPack instanceof ReviewPack
&& $tenant instanceof ManagedEnvironment
&& $user instanceof User
&& $this->reviewPackHasExportArtifact($reviewPack)
&& (int) $reviewPack->managed_environment_id === (int) $tenant->getKey()
&& $user->can(Capabilities::REVIEW_PACK_VIEW, $tenant);
}
/**
* @return array<string, mixed>
*/
private function proofItem(
string $label,
string $state,
string $description,
string $color,
?string $url = null,
?string $actionLabel = null,
): array {
return [
'label' => $label,
'state' => $state,
'description' => $description,
'color' => $color,
'url' => $url,
'actionLabel' => $actionLabel,
];
}
/**
* @param array<string, mixed> $card
* @return array<string, mixed>
*/
private function proofItemFromCard(array $card): array
{
return $this->proofItem(
(string) $card['label'],
(string) ($card['path_state'] ?? $card['value']),
(string) $card['description'],
(string) $card['color'],
is_string($card['url'] ?? null) ? $card['url'] : null,
is_string($card['url'] ?? null) ? 'Open proof' : null,
);
}
private function operationProofState(OperationRun $operationRun): string
{
if ((string) $operationRun->outcome === OperationRunOutcome::Failed->value) {
return 'Failed';
}
return in_array((string) $operationRun->status, [OperationRunStatus::Queued->value, OperationRunStatus::Running->value], true)
? 'Generating'
: 'Available';
}
private function operationProofTone(OperationRun $operationRun): string
{
return $this->flowTone($this->operationProofState($operationRun));
}
private function exportProofDescription(?ReviewPack $reviewPack, ?ManagedEnvironment $tenant): string
{
if (! $reviewPack instanceof ReviewPack) {
return 'No review pack is linked, so no export artifact is available.';
}
if (! $this->reviewPackHasExportArtifact($reviewPack)) {
return 'External delivery is not configured or the review pack file metadata is missing, expired, or not ready.';
}
return $this->canDownloadReviewPack($reviewPack, $tenant)
? 'Signed download is available for authorized users.'
: 'A file-backed export exists, but this user cannot download it from the current scope.';
}
private function decisionEvidenceSummary(
?EvidenceSnapshot $snapshot,
?ReviewPack $reviewPack,
?StoredReport $storedReport,
?EnvironmentReview $review,
?ManagedEnvironment $tenant,
?OperationRun $operationRun,
): string {
$parts = [];
$parts[] = $tenant instanceof ManagedEnvironment ? 'Environment scope selected' : 'No environment selected';
$parts[] = 'Snapshot: '.$this->snapshotFlowState($snapshot);
$parts[] = 'Stored report: '.$this->storedReportFlowState($snapshot, $storedReport);
$parts[] = 'Review pack: '.$this->reviewPackFlowState($snapshot, $storedReport, $reviewPack);
$parts[] = 'Customer-safe output: '.$this->customerSafeFlowState($snapshot, $reviewPack, $review);
$parts[] = 'Export: '.$this->exportFlowState($snapshot, $reviewPack, $tenant);
if ($operationRun instanceof OperationRun) {
$parts[] = OperationRunLinks::identifier($operationRun);
}
return implode(' · ', $parts);
}
/**
* @param array{label:string,url:string}|null $primaryAction
*/
private function decisionActionHelper(string $state, ?ManagedEnvironment $tenant, ?array $primaryAction): ?string
{
if ($primaryAction !== null) {
return null;
}
if (! $tenant instanceof ManagedEnvironment) {
return 'Use the Environment scope control in the top bar to choose an authorized environment.';
}
return match ($state) {
'no_snapshot' => 'Evidence generation requires evidence management capability.',
'pack_required' => 'Review pack generation requires review-pack management capability.',
'export_available' => 'Download requires review-pack view capability.',
default => 'No authorized repo-backed primary action is available for this state.',
};
}
/**
* @param array{label:string,url:string}|null $primaryAction
*/
private function decisionActionDescription(string $state, ?array $primaryAction): string
{
if ($primaryAction === null) {
return $state === 'source_unavailable'
? 'Choose an environment before evaluating evidence, reports, review packs, or export readiness.'
: 'No capability-backed action is available for the current state.';
}
return match ($state) {
'no_snapshot' => 'Creates the evidence snapshot required before reports or review packs can be trusted.',
'snapshot_generating', 'pack_generating' => 'Opens the linked operation so progress can be verified before using the output.',
'snapshot_failed', 'pack_failed' => 'Opens failed operation proof before retrying or sharing any output.',
'snapshot_stale' => 'Opens the stale snapshot so evidence can be refreshed from the correct scope.',
'report_missing' => 'Opens snapshot proof; report generation remains on the supported report surface.',
'pack_required' => 'Opens the environment review-pack surface where generation stays capability-gated.',
'customer_review_required' => 'Opens the customer review workspace before any external sharing decision.',
'customer_safe_ready' => 'Opens the customer review workspace that backs the readiness claim.',
'export_available' => 'Downloads the signed export for authorized users.',
'export_unavailable' => 'Opens review-pack proof to inspect missing, expired, or unauthorized export state.',
default => 'Opens the most relevant repo-backed proof surface for this state.',
};
}
/**
* @return Collection<int, EvidenceSnapshot>
*/
@ -382,7 +1108,7 @@ private function evidenceScopeLabel(): string
private function latestReviewPackForSnapshot(EvidenceSnapshot $snapshot): ?ReviewPack
{
$reviewPack = $snapshot->reviewPacks
->sortByDesc(fn (ReviewPack $pack): int => $pack->generated_at?->getTimestamp() ?? 0)
->sortByDesc(fn (ReviewPack $pack): int => $pack->generated_at?->getTimestamp() ?? $pack->created_at?->getTimestamp() ?? 0)
->first();
return $reviewPack instanceof ReviewPack ? $reviewPack : null;
@ -419,6 +1145,17 @@ private function latestStoredReportForTenant(ManagedEnvironment $tenant): ?Store
private function primaryOperationRun(?EvidenceSnapshot $snapshot, ?ReviewPack $reviewPack): ?OperationRun
{
$run = $reviewPack?->operationRun;
if (
$reviewPack instanceof ReviewPack
&& $run instanceof OperationRun
&& in_array((string) $reviewPack->status, [ReviewPackStatus::Queued->value, ReviewPackStatus::Generating->value, ReviewPackStatus::Failed->value], true)
&& $this->canViewOperationRun($run)
) {
return $run;
}
$run = $snapshot?->operationRun;
if ($run instanceof OperationRun && $this->canViewOperationRun($run)) {
@ -442,8 +1179,94 @@ private function canViewOperationRun(OperationRun $run): bool
/**
* @return array{label:string,url:string}|null
*/
private function primaryEvidenceAction(?EvidenceSnapshot $snapshot, ?ReviewPack $reviewPack, ?StoredReport $storedReport, ?OperationRun $operationRun): ?array
{
private function primaryEvidenceAction(
string $state,
?EvidenceSnapshot $snapshot,
?ReviewPack $reviewPack,
?StoredReport $storedReport,
?EnvironmentReview $review,
?ManagedEnvironment $tenant,
?OperationRun $operationRun,
): ?array {
if (in_array($state, ['snapshot_generating', 'snapshot_failed', 'pack_generating', 'pack_failed'], true) && $operationRun instanceof OperationRun) {
return [
'label' => $state === 'snapshot_generating' || $state === 'pack_generating'
? 'View operation progress'
: 'Review operation',
'url' => OperationRunLinks::tenantlessView($operationRun),
];
}
if ($state === 'no_snapshot' && $tenant instanceof ManagedEnvironment && $this->canManageEvidence($tenant)) {
return [
'label' => 'Generate evidence snapshot',
'url' => EvidenceSnapshotResource::getUrl('index', tenant: $tenant, panel: 'admin'),
];
}
if ($state === 'snapshot_stale' && $snapshot instanceof EvidenceSnapshot && $snapshot->tenant instanceof ManagedEnvironment && $this->canManageEvidence($snapshot->tenant)) {
return [
'label' => 'Refresh evidence snapshot',
'url' => EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $snapshot->tenant, panel: 'admin'),
];
}
if ($state === 'pack_required' && $tenant instanceof ManagedEnvironment && $this->canManageReviewPacks($tenant)) {
return [
'label' => 'Generate review pack',
'url' => ReviewPackResource::getUrl('index', tenant: $tenant, panel: 'admin'),
];
}
if ($snapshot instanceof EvidenceSnapshot && $this->isEmptyEvidenceSnapshot($snapshot) && $snapshot->tenant instanceof ManagedEnvironment) {
return [
'label' => 'Open evidence snapshot',
'url' => EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $snapshot->tenant, panel: 'admin'),
];
}
if ($state === 'customer_review_required' && $tenant instanceof ManagedEnvironment) {
return [
'label' => 'Review customer output',
'url' => CustomerReviewWorkspace::environmentFilterUrl($tenant),
];
}
if ($state === 'export_available' && $this->canDownloadReviewPack($reviewPack, $tenant)) {
return [
'label' => 'Download export',
'url' => app(ReviewPackService::class)->generateDownloadUrl($reviewPack),
];
}
if ($state === 'customer_safe_ready' && $tenant instanceof ManagedEnvironment) {
return [
'label' => 'Open customer workspace',
'url' => CustomerReviewWorkspace::environmentFilterUrl($tenant),
];
}
if ($state === 'report_missing' && $snapshot instanceof EvidenceSnapshot && $snapshot->tenant instanceof ManagedEnvironment) {
return [
'label' => 'Open evidence snapshot',
'url' => EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $snapshot->tenant, panel: 'admin'),
];
}
if ($state === 'export_unavailable' && $reviewPack instanceof ReviewPack && $reviewPack->tenant instanceof ManagedEnvironment) {
return [
'label' => 'Open review pack',
'url' => ReviewPackResource::getUrl('view', ['record' => $reviewPack], tenant: $reviewPack->tenant, panel: 'admin'),
];
}
if ($review instanceof EnvironmentReview && $tenant instanceof ManagedEnvironment) {
return [
'label' => 'Open customer workspace',
'url' => CustomerReviewWorkspace::environmentFilterUrl($tenant),
];
}
if ($snapshot instanceof EvidenceSnapshot && $snapshot->tenant instanceof ManagedEnvironment) {
return [
'label' => 'Open evidence snapshot',
@ -475,6 +1298,20 @@ private function primaryEvidenceAction(?EvidenceSnapshot $snapshot, ?ReviewPack
return null;
}
private function canManageEvidence(ManagedEnvironment $tenant): bool
{
$user = auth()->user();
return $user instanceof User && $user->can(Capabilities::EVIDENCE_MANAGE, $tenant);
}
private function canManageReviewPacks(ManagedEnvironment $tenant): bool
{
$user = auth()->user();
return $user instanceof User && $user->can(Capabilities::REVIEW_PACK_MANAGE, $tenant);
}
/**
* @return array<string, mixed>
*/
@ -828,6 +1665,7 @@ private function latestAccessibleSnapshots(): Collection
'tenant',
'operationRun',
'reviewPacks.operationRun',
'reviewPacks.environmentReview.currentExportReviewPack',
'items',
])
->where('workspace_id', $this->workspaceId())

View File

@ -2,6 +2,15 @@
@php
$environmentFilterChip = $this->environmentFilterChip();
$payload = $this->evidenceDisclosurePayload();
$decisionCard = $payload['decision_card'];
$statusBadgeClasses = static fn (?string $tone): string => 'inline-flex items-center rounded-md border px-2 py-0.5 text-left text-xs font-medium leading-5 whitespace-normal break-words '.match ($tone) {
'danger' => 'border-danger-200 bg-danger-50 text-danger-700 dark:border-danger-800 dark:bg-danger-500/10 dark:text-danger-300',
'success' => 'border-success-200 bg-success-50 text-success-700 dark:border-success-800 dark:bg-success-500/10 dark:text-success-300',
'warning' => 'border-warning-200 bg-warning-50 text-warning-700 dark:border-warning-800 dark:bg-warning-500/10 dark:text-warning-300',
'info' => 'border-info-200 bg-info-50 text-info-700 dark:border-info-800 dark:bg-info-500/10 dark:text-info-300',
'primary' => 'border-primary-200 bg-primary-50 text-primary-700 dark:border-primary-800 dark:bg-primary-500/10 dark:text-primary-300',
default => 'border-gray-200 bg-gray-50 text-gray-700 dark:border-white/10 dark:bg-white/5 dark:text-gray-200',
};
@endphp
<div class="space-y-4">
@ -18,11 +27,11 @@
</div>
<h2 class="text-xl font-semibold text-gray-950 dark:text-white">
What proof is available for this scope?
{{ $decisionCard['question'] }}
</h2>
<p class="text-sm leading-6 text-gray-600 dark:text-gray-300">
{{ $payload['scope_description'] }}
What proof is available for this scope? {{ $payload['scope_description'] }}
</p>
</div>
@ -40,97 +49,162 @@
<div class="grid grid-cols-1 gap-4 lg:grid-cols-[minmax(0,1fr)_22rem]" data-testid="evidence-disclosure-workbench">
<main class="min-w-0 space-y-4" data-testid="evidence-proof-primary">
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900">
<div class="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div class="space-y-2">
<div class="text-xs font-semibold uppercase text-gray-500 dark:text-gray-400">
Primary proof path
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="min-w-0 space-y-3">
<div class="flex flex-wrap items-center gap-2">
<div class="text-xs font-semibold uppercase text-gray-500 dark:text-gray-400">
Primary proof path
</div>
<span data-testid="evidence-review-pack-status-badge" class="{{ $statusBadgeClasses($decisionCard['tone'] ?? 'gray') }}">
{{ $decisionCard['status'] }}
</span>
</div>
<h3 class="text-lg font-semibold text-gray-950 dark:text-white">
{{ $payload['primary_title'] }}
</h3>
<div>
<h3 class="text-lg font-semibold text-gray-950 dark:text-white">
{{ $payload['primary_title'] }}
</h3>
<p class="max-w-3xl text-sm leading-6 text-gray-600 dark:text-gray-300">
{{ $payload['primary_summary'] }}
</p>
<p class="mt-1 max-w-3xl text-sm leading-6 text-gray-600 dark:text-gray-300">
{{ $payload['primary_summary'] }}
</p>
</div>
@if ($payload['primary_state'] !== null)
<div class="space-y-3 rounded-lg border border-gray-200 bg-gray-50 p-3 dark:border-white/10 dark:bg-white/5">
<x-filament::badge :color="$payload['primary_state']['color']">
{{ $payload['primary_state']['label'] }}
</x-filament::badge>
@if ($payload['primary_proof_state'] !== null)
<div class="rounded-lg border border-warning-200 bg-warning-50 px-3 py-2 text-sm leading-6 text-warning-800 dark:border-warning-800 dark:bg-warning-500/10 dark:text-warning-200">
<div class="font-medium">
{{ $payload['primary_proof_state']['reason'] }}
</div>
<div class="grid gap-2 text-sm text-gray-600 dark:text-gray-300 sm:grid-cols-2">
<div>
<div class="text-xs font-semibold uppercase text-gray-500 dark:text-gray-400">
Reason
</div>
<p class="mt-1 leading-5">
{{ $payload['primary_state']['reason'] }}
</p>
</div>
<div>
<div class="text-xs font-semibold uppercase text-gray-500 dark:text-gray-400">
Impact
</div>
<p class="mt-1 leading-5">
{{ $payload['primary_state']['impact'] }}
</p>
</div>
<div class="mt-1">
{{ $payload['primary_proof_state']['impact'] }}
</div>
</div>
@endif
<dl class="overflow-hidden rounded-lg border border-gray-200 bg-gray-50/70 dark:border-white/10 dark:bg-white/5">
<div class="grid gap-1 px-4 py-3 sm:grid-cols-[8rem_minmax(0,1fr)] sm:gap-4">
<dt class="text-xs font-semibold text-gray-500 dark:text-gray-400">
{{ $decisionCard['statusLabel'] }}
</dt>
<dd class="text-sm font-medium text-gray-950 dark:text-white">
{{ $decisionCard['status'] }}
</dd>
</div>
<div class="grid gap-1 border-t border-gray-200 px-4 py-3 dark:border-white/10 sm:grid-cols-[8rem_minmax(0,1fr)] sm:gap-4">
<dt class="text-xs font-semibold text-gray-500 dark:text-gray-400">
{{ $decisionCard['reasonLabel'] }}
</dt>
<dd class="text-sm leading-6 text-gray-700 dark:text-gray-200">
{{ $decisionCard['reason'] }}
</dd>
</div>
<div class="grid gap-1 border-t border-gray-200 px-4 py-3 dark:border-white/10 sm:grid-cols-[8rem_minmax(0,1fr)] sm:gap-4">
<dt class="text-xs font-semibold text-gray-500 dark:text-gray-400">
{{ $decisionCard['impactLabel'] }}
</dt>
<dd class="text-sm leading-6 text-gray-700 dark:text-gray-200">
{{ $decisionCard['impact'] }}
</dd>
</div>
<div class="grid gap-1 border-t border-gray-200 px-4 py-3 dark:border-white/10 sm:grid-cols-[8rem_minmax(0,1fr)] sm:gap-4">
<dt class="text-xs font-semibold text-gray-500 dark:text-gray-400">
{{ $decisionCard['evidenceLabel'] }}
</dt>
<dd class="text-sm leading-6 text-gray-700 dark:text-gray-200">
{{ $decisionCard['evidence'] }}
</dd>
</div>
<div class="grid gap-1 border-t border-gray-200 px-4 py-3 dark:border-white/10 sm:grid-cols-[8rem_minmax(0,1fr)] sm:gap-4">
<dt class="text-xs font-semibold text-gray-500 dark:text-gray-400">
{{ $decisionCard['nextActionLabel'] }}
</dt>
<dd class="space-y-1 text-sm leading-6 text-gray-700 dark:text-gray-200">
<div class="font-medium text-gray-950 dark:text-white">
{{ $decisionCard['actionLabel'] }}
</div>
<p>
{{ $decisionCard['actionDescription'] }}
</p>
</dd>
</div>
</dl>
</div>
@if ($payload['primary_action'])
@if ($decisionCard['actionUrl'])
<x-filament::button
tag="a"
:href="$payload['primary_action']['url']"
:href="$decisionCard['actionUrl']"
icon="heroicon-o-arrow-top-right-on-square"
class="shrink-0 self-start whitespace-nowrap"
data-testid="evidence-primary-proof-action"
>
{{ $payload['primary_action']['label'] }}
{{ $decisionCard['actionLabel'] }}
</x-filament::button>
@else
<x-filament::badge color="gray">
No open proof action
</x-filament::badge>
<div class="shrink-0 self-start">
<x-filament::button color="gray" disabled data-testid="evidence-primary-proof-action">
{{ $decisionCard['actionLabel'] }}
</x-filament::button>
@if ($decisionCard['helperText'])
<p class="mt-2 max-w-64 text-xs leading-5 text-gray-500 dark:text-gray-400">
{{ $decisionCard['helperText'] }}
</p>
@endif
</div>
@endif
</div>
</div>
<div class="grid gap-3 md:grid-cols-2 xl:grid-cols-4" data-testid="evidence-proof-cards">
@foreach ($payload['cards'] as $card)
<div class="flex h-full rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900">
<div class="flex min-h-36 grow flex-col gap-3">
<div class="text-xs font-semibold uppercase text-gray-500 dark:text-gray-400">
{{ $card['label'] }}
@if (! empty($payload['readiness_flow']))
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900">
@include('filament.components.product-process-flow-horizontal', [
'title' => 'Evidence readiness flow',
'subtitle' => 'Customer-safe evidence requires source data, evidence snapshot, stored report, review pack, and export readiness.',
'ariaLabel' => 'Evidence readiness pipeline',
'steps' => $payload['readiness_flow'],
'flowTestId' => 'evidence-readiness-flow',
'stepTestId' => 'evidence-readiness-step',
'connectorTestId' => 'evidence-readiness-connector',
'badgeTestId' => 'evidence-review-pack-status-badge',
'statusBadgeClasses' => $statusBadgeClasses,
])
</div>
@endif
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900" data-testid="evidence-review-pack-coverage">
<div class="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between">
<h3 class="text-sm font-semibold text-gray-950 dark:text-white">
Review pack contents / coverage
</h3>
<p class="text-xs leading-5 text-gray-500 dark:text-gray-400">
Repo-backed values only.
</p>
</div>
<p class="mt-2 text-sm leading-6 text-gray-600 dark:text-gray-300">
{{ $payload['review_pack_coverage']['description'] }}
</p>
@if (! empty($payload['review_pack_coverage']['items']))
<div class="mt-3 grid gap-2 sm:grid-cols-2 xl:grid-cols-3">
@foreach ($payload['review_pack_coverage']['items'] as $coverageItem)
<div class="rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 dark:border-white/10 dark:bg-white/5">
<div class="text-xs font-semibold text-gray-500 dark:text-gray-400">
{{ $coverageItem['label'] }}
</div>
<div class="mt-1 text-sm font-medium text-gray-950 dark:text-white">
{{ $coverageItem['value'] }}
</div>
</div>
<div>
<x-filament::badge :color="$card['color']">
{{ $card['value'] }}
</x-filament::badge>
</div>
<p class="text-sm leading-5 text-gray-600 dark:text-gray-300">
{{ $card['description'] }}
</p>
<div class="mt-auto flex flex-wrap items-center gap-2 text-xs text-gray-500 dark:text-gray-400">
<span>{{ $card['meta'] }}</span>
@if ($card['url'])
<a class="font-medium text-primary-600 hover:underline dark:text-primary-400" href="{{ $card['url'] }}">
Open
</a>
@endif
</div>
</div>
@endforeach
</div>
@endforeach
@endif
</div>
</main>
@ -138,16 +212,16 @@ class="shrink-0 self-start whitespace-nowrap"
<div class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-gray-900">
<div class="space-y-1">
<h3 class="text-sm font-semibold text-gray-950 dark:text-white">
Evidence path
Evidence proof
</h3>
<p class="text-xs leading-5 text-gray-500 dark:text-gray-400">
Only repo-supported proof sources are listed. Missing pieces stay explicit.
Evidence path rows list only repo-supported proof sources. Missing pieces stay explicit.
</p>
</div>
<div class="mt-4 space-y-3">
@foreach ($payload['path_items'] as $item)
<div class="rounded-lg border border-gray-200 p-3 text-sm dark:border-white/10">
@foreach ($payload['proof_items'] as $item)
<div class="rounded-lg border border-gray-200 p-3 text-sm dark:border-white/10" data-testid="evidence-proof-item" data-proof-label="{{ $item['label'] }}">
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<div class="font-medium text-gray-950 dark:text-white">
@ -157,19 +231,14 @@ class="shrink-0 self-start whitespace-nowrap"
{{ $item['description'] }}
</p>
</div>
<x-filament::badge
:color="$item['color']"
size="sm"
class="shrink-0 whitespace-nowrap"
data-testid="evidence-path-state-badge"
>
<span data-testid="evidence-path-state-badge" class="{{ $statusBadgeClasses($item['color'] ?? 'gray') }}">
{{ $item['state'] }}
</x-filament::badge>
</span>
</div>
@if ($item['url'])
<a class="mt-2 inline-flex text-xs font-medium text-primary-600 hover:underline dark:text-primary-400" href="{{ $item['url'] }}">
Open proof
{{ $item['actionLabel'] ?? 'Open proof' }}
</a>
@endif
</div>

View File

@ -0,0 +1,352 @@
<?php
declare(strict_types=1);
use App\Models\EnvironmentReview;
use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Models\ReviewPack;
use App\Models\StoredReport;
use App\Models\User;
use App\Support\EnvironmentReviewCompletenessState;
use App\Support\EnvironmentReviewStatus;
use App\Support\Evidence\EvidenceCompletenessState;
use App\Support\Evidence\EvidenceSnapshotStatus;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;
use App\Support\ReviewPackStatus;
use App\Support\Workspaces\WorkspaceContext;
pest()->browser()->timeout(60_000);
function spec337BrowserScreenshotName(string $name): string
{
return 'spec337-evidence-review-pack-'.$name;
}
function spec337CopyBrowserScreenshot(string $name): void
{
$filename = spec337BrowserScreenshotName($name).'.png';
$source = base_path('tests/Browser/Screenshots/'.$filename);
$targetDirectory = repo_path('specs/337-evidence-review-pack-product-process-flow-alignment/artifacts/screenshots');
if (! is_dir($targetDirectory)) {
@mkdir($targetDirectory, 0755, true);
}
if (! is_file($source)) {
$source = \Pest\Browser\Support\Screenshot::path($filename);
}
for ($attempt = 0; $attempt < 10 && ! is_file($source); $attempt++) {
usleep(100_000);
clearstatcache(true, $source);
}
if (is_file($source) && is_dir($targetDirectory) && is_writable($targetDirectory)) {
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$name.'.png');
}
}
function spec337AuthenticateBrowser(mixed $test, User $user, ManagedEnvironment $environment): void
{
$workspaceId = (int) $environment->workspace_id;
$test->actingAs($user)->withSession([
WorkspaceContext::SESSION_KEY => $workspaceId,
WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [
(string) $workspaceId => (int) $environment->getKey(),
],
]);
session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
(string) $workspaceId => (int) $environment->getKey(),
]);
setAdminPanelContext($environment);
}
function spec337BrowserEnvironmentFor(User $user, ManagedEnvironment $baseEnvironment, string $name): ManagedEnvironment
{
$environment = ManagedEnvironment::factory()->active()->create([
'workspace_id' => (int) $baseEnvironment->workspace_id,
'name' => $name,
]);
createUserWithTenant(tenant: $environment, user: $user, role: 'owner', workspaceRole: 'manager');
return $environment;
}
it('Spec337 smokes Evidence Review Pack product process flow states', function (): void {
[$user, $missingEnvironment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
$missingEnvironment->forceFill(['name' => 'Spec337 Evidence Snapshot Required'])->save();
$generatingEnvironment = spec337BrowserEnvironmentFor($user, $missingEnvironment, 'Spec337 Evidence Generating');
$generatingRun = OperationRun::factory()->forTenant($generatingEnvironment)->create([
'type' => OperationRunType::EvidenceSnapshotGenerate->value,
'status' => OperationRunStatus::Running->value,
'outcome' => OperationRunOutcome::Pending->value,
'started_at' => now()->subMinute(),
'initiator_name' => 'Spec337 Browser Operator',
]);
spec337BrowserCreateEvidenceSnapshot($generatingEnvironment, [
'operation_run_id' => (int) $generatingRun->getKey(),
'status' => EvidenceSnapshotStatus::Generating->value,
'generated_at' => null,
]);
$storedReportRequiredEnvironment = spec337BrowserEnvironmentFor($user, $missingEnvironment, 'Spec337 Stored Report Required');
spec337BrowserCreateEvidenceSnapshot($storedReportRequiredEnvironment);
$reviewPackRequiredEnvironment = spec337BrowserEnvironmentFor($user, $missingEnvironment, 'Spec337 Review Pack Required');
spec337BrowserCreateEvidenceSnapshot($reviewPackRequiredEnvironment);
spec337BrowserCreateStoredReport($reviewPackRequiredEnvironment);
$availableEnvironment = spec337BrowserEnvironmentFor($user, $missingEnvironment, 'Spec337 Review Pack Available');
$availableSnapshot = spec337BrowserCreateEvidenceSnapshot($availableEnvironment);
spec337BrowserCreateStoredReport($availableEnvironment);
spec337BrowserCreatePublishedReviewWithReadyPack($availableEnvironment, $user, $availableSnapshot);
$exportUnavailableEnvironment = spec337BrowserEnvironmentFor($user, $missingEnvironment, 'Spec337 Export Unavailable');
$exportUnavailableSnapshot = spec337BrowserCreateEvidenceSnapshot($exportUnavailableEnvironment);
spec337BrowserCreateStoredReport($exportUnavailableEnvironment);
ReviewPack::factory()->ready()->create([
'managed_environment_id' => (int) $exportUnavailableEnvironment->getKey(),
'workspace_id' => (int) $exportUnavailableEnvironment->workspace_id,
'evidence_snapshot_id' => (int) $exportUnavailableSnapshot->getKey(),
'file_disk' => null,
'file_path' => null,
'file_size' => null,
'sha256' => null,
]);
spec337AuthenticateBrowser($this, $user, $missingEnvironment);
$page = visit(route('admin.evidence.overview', [
'environment_id' => (int) $missingEnvironment->getKey(),
]))
->resize(1440, 1100)
->waitForText('Evidence snapshot required')
->assertSee('Is this evidence package ready for customer or auditor consumption?')
->assertSee('Generate evidence snapshot')
->assertSee('Evidence readiness flow')
->assertScript('document.querySelectorAll("[data-testid=\"evidence-readiness-step\"]").length === 6', true)
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepState === "Missing"', true)
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepCurrentBlocker === "true"', true)
->assertScript('document.querySelector("[data-testid=\"evidence-disclosure-diagnostics\"]")?.open === false', true)
->assertDontSee('raw payload should stay hidden')
->assertDontSee('provider response should stay hidden')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page->screenshot(true, spec337BrowserScreenshotName('01-evidence-snapshot-required'));
spec337CopyBrowserScreenshot('01-evidence-snapshot-required');
$page = visit(route('admin.evidence.overview', [
'environment_id' => (int) $generatingEnvironment->getKey(),
]))
->waitForText('Evidence generation in progress')
->assertSee('View operation progress')
->assertSee('Operation proof')
->assertSee('Spec337 Browser Operator')
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepState === "Generating"', true)
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepCurrentBlocker === "true"', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page->screenshot(true, spec337BrowserScreenshotName('02-evidence-generating'));
spec337CopyBrowserScreenshot('02-evidence-generating');
$page = visit(route('admin.evidence.overview', [
'environment_id' => (int) $storedReportRequiredEnvironment->getKey(),
]))
->waitForText('Stored report required')
->assertSee('Open evidence snapshot')
->assertScript('document.querySelector("[data-step-label=\"Stored report\"]")?.dataset.stepState === "Missing"', true)
->assertScript('document.querySelector("[data-step-label=\"Stored report\"]")?.dataset.stepCurrentBlocker === "true"', true)
->assertDontSee('Download export')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page->screenshot(true, spec337BrowserScreenshotName('03-stored-report-required'));
spec337CopyBrowserScreenshot('03-stored-report-required');
$page = visit(route('admin.evidence.overview', [
'environment_id' => (int) $reviewPackRequiredEnvironment->getKey(),
]))
->waitForText('Review pack required')
->assertSee('Generate review pack')
->assertScript('document.querySelector("[data-step-label=\"Review pack\"]")?.dataset.stepState === "Required"', true)
->assertScript('document.querySelector("[data-step-label=\"Review pack\"]")?.dataset.stepCurrentBlocker === "true"', true)
->assertScript('document.querySelector("[data-testid=\"evidence-primary-proof-action\"]") instanceof HTMLAnchorElement', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page->screenshot(true, spec337BrowserScreenshotName('04-review-pack-required'));
spec337CopyBrowserScreenshot('04-review-pack-required');
$page = visit(route('admin.evidence.overview', [
'environment_id' => (int) $availableEnvironment->getKey(),
]))
->waitForText('Review pack export available')
->assertSee('Download export')
->assertSee('Customer-safe output')
->assertSee('Review pack contents / coverage')
->assertScript('document.querySelector("[data-step-label=\"Review pack\"]")?.dataset.stepState === "Available"', true)
->assertScript('document.querySelector("[data-step-label=\"Customer-safe output\"]")?.dataset.stepState === "Ready"', true)
->assertScript('document.querySelector("[data-step-label=\"Export / delivery\"]")?.dataset.stepState === "Available"', true)
->assertScript('Array.from(document.querySelectorAll("[data-testid=\"evidence-review-pack-status-badge\"]")).every((badge) => !badge.innerText.includes("...") && getComputedStyle(badge).overflow !== "hidden" && getComputedStyle(badge).textOverflow !== "ellipsis")', true)
->assertDontSee('Auditor-ready')
->assertDontSee('environment is healthy')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page->screenshot(true, spec337BrowserScreenshotName('05-review-pack-available'));
spec337CopyBrowserScreenshot('05-review-pack-available');
$page->screenshot(true, spec337BrowserScreenshotName('06-customer-safe-output-state'));
spec337CopyBrowserScreenshot('06-customer-safe-output-state');
$page = visit(route('admin.evidence.overview', [
'environment_id' => (int) $exportUnavailableEnvironment->getKey(),
]))
->waitForText('Export unavailable')
->assertSee('Open review pack')
->assertScript('document.querySelector("[data-step-label=\"Export / delivery\"]")?.dataset.stepState === "Required"', true)
->assertDontSee('Download export')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page->screenshot(true, spec337BrowserScreenshotName('07-export-unavailable'));
spec337CopyBrowserScreenshot('07-export-unavailable');
$page->assertScript('document.querySelector("[data-testid=\"evidence-disclosure-diagnostics\"]")?.open === false', true);
$page->screenshot(true, spec337BrowserScreenshotName('08-diagnostics-collapsed'));
spec337CopyBrowserScreenshot('08-diagnostics-collapsed');
$page->script("document.documentElement.classList.add('dark');");
$page->script('window.scrollTo(0, 0);');
$page->assertScript('document.documentElement.classList.contains("dark")', true);
$page->screenshot(true, spec337BrowserScreenshotName('09-dark-mode'));
spec337CopyBrowserScreenshot('09-dark-mode');
});
/**
* @param array<string, mixed> $attributes
*/
function spec337BrowserCreateEvidenceSnapshot(ManagedEnvironment $environment, array $attributes = []): EvidenceSnapshot
{
$snapshot = EvidenceSnapshot::query()->create(array_replace([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'status' => EvidenceSnapshotStatus::Active->value,
'fingerprint' => sha1('spec337-browser-snapshot-'.$environment->getKey().'-'.microtime(true)),
'completeness_state' => EvidenceCompletenessState::Complete->value,
'summary' => [
'dimension_count' => 5,
'missing_dimensions' => 0,
'stale_dimensions' => 0,
'raw_payload' => 'raw payload should stay hidden',
'provider_response' => 'provider response should stay hidden',
],
'generated_at' => now(),
'expires_at' => now()->addDays(30),
], $attributes));
$snapshot->items()->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'dimension_key' => 'permission_posture',
'state' => (string) ($snapshot->completeness_state ?: EvidenceCompletenessState::Complete->value),
'required' => true,
'source_kind' => 'stored_report',
'source_record_type' => StoredReport::class,
'source_record_id' => null,
'source_fingerprint' => null,
'measured_at' => now(),
'freshness_at' => now(),
'summary_payload' => [
'label' => 'Permission posture',
'state' => 'complete',
],
'sort_order' => 1,
]);
return $snapshot->refresh();
}
function spec337BrowserCreateStoredReport(ManagedEnvironment $environment): StoredReport
{
return StoredReport::factory()->permissionPosture([
'raw_payload' => 'raw payload should stay hidden',
'provider_response' => 'provider response should stay hidden',
])->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'fingerprint' => sha1('spec337-browser-report-'.$environment->getKey().'-'.microtime(true)),
]);
}
function spec337BrowserCreatePublishedReviewWithReadyPack(
ManagedEnvironment $environment,
User $user,
EvidenceSnapshot $snapshot,
): ReviewPack {
$review = EnvironmentReview::query()->create([
'workspace_id' => (int) $environment->workspace_id,
'managed_environment_id' => (int) $environment->getKey(),
'evidence_snapshot_id' => (int) $snapshot->getKey(),
'initiated_by_user_id' => (int) $user->getKey(),
'published_by_user_id' => (int) $user->getKey(),
'fingerprint' => sha1('spec337-browser-review-'.$environment->getKey().'-'.microtime(true)),
'status' => EnvironmentReviewStatus::Published->value,
'completeness_state' => EnvironmentReviewCompletenessState::Complete->value,
'summary' => [
'governance_package' => [
'decision_summary' => [
'status' => 'ready',
'evidence_state' => EnvironmentReviewCompletenessState::Complete->value,
'decision_data_state' => 'complete',
],
],
],
'generated_at' => now()->subMinutes(5),
'published_at' => now()->subMinutes(3),
]);
$run = OperationRun::factory()->forTenant($environment)->create([
'type' => OperationRunType::ReviewPackGenerate->value,
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Succeeded->value,
'started_at' => now()->subMinutes(4),
'completed_at' => now()->subMinutes(2),
'initiator_name' => $user->name,
]);
$pack = ReviewPack::factory()->ready()->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'evidence_snapshot_id' => (int) $snapshot->getKey(),
'environment_review_id' => (int) $review->getKey(),
'operation_run_id' => (int) $run->getKey(),
'initiated_by_user_id' => (int) $user->getKey(),
'status' => ReviewPackStatus::Ready->value,
'summary' => [
'finding_count' => 3,
'report_count' => 2,
'operation_count' => 1,
'section_count' => 4,
'evidence_resolution' => [
'required_dimensions' => [
'findings_summary',
'permission_posture',
'entra_admin_roles',
],
],
],
]);
$review->forceFill([
'current_export_review_pack_id' => (int) $pack->getKey(),
])->save();
return $pack->refresh();
}

View File

@ -0,0 +1,361 @@
<?php
declare(strict_types=1);
use App\Filament\Pages\Monitoring\EvidenceOverview;
use App\Models\EnvironmentReview;
use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Models\ReviewPack;
use App\Models\StoredReport;
use App\Models\User;
use App\Support\EnvironmentReviewCompletenessState;
use App\Support\EnvironmentReviewStatus;
use App\Support\Evidence\EvidenceCompletenessState;
use App\Support\Evidence\EvidenceSnapshotStatus;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;
use App\Support\ReviewPackStatus;
use App\Support\Workspaces\WorkspaceContext;
use Livewire\Livewire;
it('renders missing evidence as the first evidence readiness blocker', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
$component = spec337EvidenceOverviewLivewire($user, $environment)
->assertSee('Is this evidence package ready for customer or auditor consumption?')
->assertSee('Evidence snapshot required')
->assertSee('Review pack output cannot be trusted or exported yet.')
->assertSee('Generate evidence snapshot')
->assertSee('Evidence readiness flow')
->assertSee('Evidence proof')
->assertSee('Diagnostics - Collapsed')
->assertDontSee('raw payload should stay hidden')
->assertDontSee('provider response should stay hidden')
->assertDontSee('stack trace should stay hidden');
$content = $component->html();
spec337AssertFlowStep($content, 'Source data selected', 'Available', false);
spec337AssertFlowStep($content, 'Evidence snapshot', 'Missing', true);
spec337AssertFlowStep($content, 'Stored report', 'Unavailable', false);
spec337AssertFlowStep($content, 'Review pack', 'Unavailable', false);
spec337AssertFlowStep($content, 'Customer-safe output', 'Not ready', false);
spec337AssertFlowStep($content, 'Export / delivery', 'Unavailable', false);
expect(substr_count($content, 'data-testid="evidence-readiness-step"'))->toBe(6)
->and($content)->not->toContain('data-testid="evidence-disclosure-diagnostics" open');
});
it('renders stored report missing without inventing review-pack or export readiness', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
spec337CreateEvidenceSnapshot($environment);
$component = spec337EvidenceOverviewLivewire($user, $environment)
->assertSee('Stored report required')
->assertSee('Evidence snapshot exists, but no stored report is available for this review output.')
->assertSee('Open evidence snapshot')
->assertDontSee('Review pack export available')
->assertDontSee('Customer-safe output ready')
->assertDontSee('Download export');
$content = $component->html();
spec337AssertFlowStep($content, 'Evidence snapshot', 'Available', false);
spec337AssertFlowStep($content, 'Stored report', 'Missing', true);
spec337AssertFlowStep($content, 'Review pack', 'Unavailable', false);
spec337AssertFlowStep($content, 'Customer-safe output', 'Not ready', false);
spec337AssertFlowStep($content, 'Export / delivery', 'Unavailable', false);
});
it('renders review pack required with capability-aware primary action', function (): void {
[$owner, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
[$readonly] = createUserWithTenant(tenant: $environment, role: 'readonly', workspaceRole: 'readonly');
$snapshot = spec337CreateEvidenceSnapshot($environment);
spec337CreateStoredReport($environment);
$ownerComponent = spec337EvidenceOverviewLivewire($owner, $environment)
->assertSee('Review pack required')
->assertSee('Stored report exists, but a review pack has not been generated.')
->assertSee('Generate review pack')
->assertDontSee('Customer-safe output ready')
->assertDontSee('Review pack export available');
spec337AssertFlowStep($ownerComponent->html(), 'Review pack', 'Required', true);
spec337EvidenceOverviewLivewire($readonly, $environment)
->assertSee('Review pack required')
->assertDontSee('Generate review pack')
->assertSee('Open evidence snapshot')
->assertSee((string) $snapshot->tenant?->name);
});
it('renders repo-backed customer-safe and export-ready output without broad readiness claims', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
$snapshot = spec337CreateEvidenceSnapshot($environment);
spec337CreateStoredReport($environment);
[$review, $pack] = spec337CreatePublishedReviewWithReadyPack($environment, $user, $snapshot);
$component = spec337EvidenceOverviewLivewire($user, $environment)
->assertSee('Review pack export available')
->assertSee('A generated export artifact is available for authorized download.')
->assertSee('external sharing remains governed by workspace policy')
->assertSee('Download export')
->assertSee('Customer-safe output')
->assertSee('Ready')
->assertSee('Review pack contents / coverage')
->assertSee('Findings included')
->assertSee('Evidence dimensions')
->assertDontSee('Auditor-ready')
->assertDontSee('environment is healthy')
->assertDontSee('compliant');
$content = $component->html();
spec337AssertFlowStep($content, 'Evidence snapshot', 'Available', false);
spec337AssertFlowStep($content, 'Stored report', 'Available', false);
spec337AssertFlowStep($content, 'Review pack', 'Available', false);
spec337AssertFlowStep($content, 'Customer-safe output', 'Ready', false);
spec337AssertFlowStep($content, 'Export / delivery', 'Available', true);
expect($review->current_export_review_pack_id)->toBe($pack->getKey());
});
it('renders generating and failed operation proof separately from usable output', function (): void {
[$user, $generatingEnvironment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
$generatingRun = OperationRun::factory()->forTenant($generatingEnvironment)->create([
'type' => OperationRunType::EvidenceSnapshotGenerate->value,
'status' => OperationRunStatus::Running->value,
'outcome' => OperationRunOutcome::Pending->value,
'started_at' => now()->subMinute(),
'initiator_name' => 'Spec337 Operator',
]);
spec337CreateEvidenceSnapshot($generatingEnvironment, [
'operation_run_id' => (int) $generatingRun->getKey(),
'status' => EvidenceSnapshotStatus::Generating->value,
'generated_at' => null,
]);
$generatingComponent = spec337EvidenceOverviewLivewire($user, $generatingEnvironment)
->assertSee('Evidence generation in progress')
->assertSee('View operation progress')
->assertSee('Operation proof')
->assertSee('Generating')
->assertSee('Spec337 Operator')
->assertDontSee('Customer-safe output ready');
spec337AssertFlowStep($generatingComponent->html(), 'Evidence snapshot', 'Generating', true);
$failedEnvironment = createUserWithTenant(user: $user, role: 'owner', workspaceRole: 'manager')[1];
$failedRun = OperationRun::factory()->forTenant($failedEnvironment)->create([
'type' => OperationRunType::ReviewPackGenerate->value,
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Failed->value,
'started_at' => now()->subMinutes(3),
'completed_at' => now()->subMinute(),
'initiator_name' => 'Spec337 Reviewer',
]);
$failedSnapshot = spec337CreateEvidenceSnapshot($failedEnvironment);
spec337CreateStoredReport($failedEnvironment);
ReviewPack::factory()->failed()->create([
'managed_environment_id' => (int) $failedEnvironment->getKey(),
'workspace_id' => (int) $failedEnvironment->workspace_id,
'evidence_snapshot_id' => (int) $failedSnapshot->getKey(),
'operation_run_id' => (int) $failedRun->getKey(),
]);
$failedComponent = spec337EvidenceOverviewLivewire($user, $failedEnvironment)
->assertSee('Review pack generation failed')
->assertSee('Review operation')
->assertSee('Operation proof')
->assertSee('Failed')
->assertSee('Spec337 Reviewer')
->assertDontSee('Review pack export available');
spec337AssertFlowStep($failedComponent->html(), 'Review pack', 'Failed', true);
});
it('preserves workspace isolation and avoids legacy tenant aliases in rendered links', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
$foreignEnvironment = ManagedEnvironment::factory()->active()->create([
'name' => 'Spec337 Foreign Workspace Environment',
]);
spec337CreateEvidenceSnapshot($foreignEnvironment);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id])
->get(route('admin.evidence.overview'))
->assertOk()
->assertDontSee('Spec337 Foreign Workspace Environment');
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id])
->get(route('admin.evidence.overview', ['environment_id' => (int) $foreignEnvironment->getKey()]))
->assertNotFound();
$content = spec337EvidenceOverviewLivewire($user, $environment)->html();
expect($content)
->not->toContain('/admin/t/')
->not->toContain('tenant_id=');
}
);
function spec337EvidenceOverviewLivewire(User $user, ManagedEnvironment $environment): mixed
{
session()->put(WorkspaceContext::SESSION_KEY, (int) $environment->workspace_id);
setAdminPanelContext($environment);
return Livewire::withQueryParams([
'environment_id' => (int) $environment->getKey(),
])
->actingAs($user)
->test(EvidenceOverview::class);
}
/**
* @param array<string, mixed> $attributes
*/
function spec337CreateEvidenceSnapshot(ManagedEnvironment $environment, array $attributes = []): EvidenceSnapshot
{
$snapshot = EvidenceSnapshot::query()->create(array_replace([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'status' => EvidenceSnapshotStatus::Active->value,
'fingerprint' => sha1('spec337-snapshot-'.$environment->getKey().'-'.microtime(true)),
'completeness_state' => EvidenceCompletenessState::Complete->value,
'summary' => [
'dimension_count' => 5,
'missing_dimensions' => 0,
'stale_dimensions' => 0,
'raw_payload' => 'raw payload should stay hidden',
'provider_response' => 'provider response should stay hidden',
'stack_trace' => 'stack trace should stay hidden',
],
'generated_at' => now(),
'expires_at' => now()->addDays(30),
], $attributes));
$snapshot->items()->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'dimension_key' => 'permission_posture',
'state' => (string) ($snapshot->completeness_state ?: EvidenceCompletenessState::Complete->value),
'required' => true,
'source_kind' => 'stored_report',
'source_record_type' => StoredReport::class,
'source_record_id' => null,
'source_fingerprint' => null,
'measured_at' => now(),
'freshness_at' => now(),
'summary_payload' => [
'label' => 'Permission posture',
'state' => 'complete',
],
'sort_order' => 1,
]);
return $snapshot->load([
'tenant',
'operationRun',
'reviewPacks.operationRun',
'reviewPacks.environmentReview.currentExportReviewPack',
'items',
]);
}
function spec337CreateStoredReport(ManagedEnvironment $environment): StoredReport
{
return StoredReport::factory()->permissionPosture([
'raw_payload' => 'raw payload should stay hidden',
'provider_response' => 'provider response should stay hidden',
'stack_trace' => 'stack trace should stay hidden',
])->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'fingerprint' => sha1('spec337-report-'.$environment->getKey().'-'.microtime(true)),
]);
}
/**
* @return array{0: EnvironmentReview, 1: ReviewPack}
*/
function spec337CreatePublishedReviewWithReadyPack(
ManagedEnvironment $environment,
User $user,
EvidenceSnapshot $snapshot,
): array {
$review = EnvironmentReview::query()->create([
'workspace_id' => (int) $environment->workspace_id,
'managed_environment_id' => (int) $environment->getKey(),
'evidence_snapshot_id' => (int) $snapshot->getKey(),
'initiated_by_user_id' => (int) $user->getKey(),
'published_by_user_id' => (int) $user->getKey(),
'fingerprint' => sha1('spec337-review-'.$environment->getKey().'-'.microtime(true)),
'status' => EnvironmentReviewStatus::Published->value,
'completeness_state' => EnvironmentReviewCompletenessState::Complete->value,
'summary' => [
'governance_package' => [
'decision_summary' => [
'status' => 'ready',
'evidence_state' => EnvironmentReviewCompletenessState::Complete->value,
'decision_data_state' => 'complete',
],
],
],
'generated_at' => now()->subMinutes(5),
'published_at' => now()->subMinutes(3),
]);
$run = OperationRun::factory()->forTenant($environment)->create([
'type' => OperationRunType::ReviewPackGenerate->value,
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Succeeded->value,
'started_at' => now()->subMinutes(4),
'completed_at' => now()->subMinutes(2),
'initiator_name' => $user->name,
]);
$pack = ReviewPack::factory()->ready()->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'evidence_snapshot_id' => (int) $snapshot->getKey(),
'environment_review_id' => (int) $review->getKey(),
'operation_run_id' => (int) $run->getKey(),
'initiated_by_user_id' => (int) $user->getKey(),
'status' => ReviewPackStatus::Ready->value,
'summary' => [
'finding_count' => 3,
'report_count' => 2,
'operation_count' => 1,
'section_count' => 4,
'evidence_resolution' => [
'required_dimensions' => [
'findings_summary',
'permission_posture',
'entra_admin_roles',
],
],
],
]);
$review->forceFill([
'current_export_review_pack_id' => (int) $pack->getKey(),
])->save();
return [$review->refresh(), $pack->refresh()];
}
function spec337AssertFlowStep(string $content, string $label, string $state, bool $currentBlocker): void
{
$blocker = $currentBlocker ? 'true' : 'false';
expect($content)->toMatch(
'/data-step-label="'.preg_quote($label, '/').'"[\s\S]*?data-step-state="'.preg_quote($state, '/').'"[\s\S]*?data-step-current-blocker="'.$blocker.'"[\s\S]*?>\s*'.preg_quote($state, '/').'\s*</'
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

View File

@ -0,0 +1,71 @@
# Requirements Checklist: Spec 337 - Evidence Path / Review Pack Product Process Flow Alignment
Purpose: validate the prepared spec artifacts before runtime implementation begins.
## Spec Identity
- [x] Spec number is reconciled to current repo state (`337`, because `336` already exists).
- [x] Branch name matches spec directory.
- [x] Input source and numbering reconciliation are documented.
- [x] Runtime posture forbids backend evidence/report/review-pack/export engine rewrites.
## Constitution / Spec Candidate Gate
- [x] Spec Candidate Check is completed.
- [x] Problem, today's failure, user-visible improvement, smallest version, non-goals, complexity, why-now, why-not-local, approval class, red flags, score, and decision are filled.
- [x] Proportionality review is included for the possible derived presenter.
- [x] No new persisted truth, enum family, lifecycle family, or generic workflow framework is proposed.
- [x] Shared pattern first is explicit through Spec 332 Product Process Flow reuse.
## Repo Truth
- [x] `repo-truth-map.md` exists.
- [x] Evidence snapshot source is classified.
- [x] Stored report source is classified.
- [x] Review pack source is classified.
- [x] Environment review / Customer Review Workspace relationship is classified.
- [x] OperationRun proof relationship is classified.
- [x] Export/download artifact relationship is classified.
- [x] Evidence freshness source is classified.
- [x] Customer-safe state source is classified.
- [x] RBAC/capabilities and routes/surfaces are classified.
- [x] Existing tests are listed.
- [x] Unavailable/deferred states are called out instead of invented.
## State Contract
- [x] `evidence-review-pack-state-contract.md` exists.
- [x] Default flow steps are defined.
- [x] Presentation vocabulary is explicitly not a new enum family.
- [x] Missing, generating, failed, stale, report missing, review-pack required, review-pack generating, review-pack failed, customer-safe review required, customer-safe ready, export available, and export unavailable states are covered.
- [x] Each state includes visible status, reason, impact, primary next action, flow gate states, proof, customer-safe state, export state, and diagnostics default.
- [x] False customer-safe, auditor-ready, complete, and export-ready claims are forbidden.
## UI / Filament Guardrails
- [x] UI Surface Impact is completed and does not claim "No UI surface impact".
- [x] UI/Productization Coverage is completed.
- [x] Decision-first surface role is completed.
- [x] Audience-aware disclosure is completed.
- [x] UI/UX surface classification is completed.
- [x] Operator surface contract is completed.
- [x] Livewire v4 / Filament v5 posture is explicit.
- [x] Provider registration location remains `apps/platform/bootstrap/providers.php`.
- [x] Global search state is addressed for relevant resources.
- [x] No new destructive actions are proposed.
- [x] Asset/deployment posture is explicit: no global asset or `filament:assets` change expected.
## Testing / Validation
- [x] Feature test file path is specified.
- [x] Browser smoke file path is specified.
- [x] Required screenshots path and filenames are specified.
- [x] Required assertions cover missing evidence, report missing, review-pack required, review-pack available, OperationRun proof, RBAC/context, collapsed diagnostics, and no raw JSON.
- [x] Validation commands are listed.
- [x] Unreachable browser states must be documented rather than faked.
## Implementation Readiness
- [x] `spec.md`, `plan.md`, `tasks.md`, `repo-truth-map.md`, `evidence-review-pack-state-contract.md`, `checklists/requirements.md`, and screenshot directory are present.
- [x] Runtime implementation has not started in this preparation step.
- [x] Recommended next prompt is implementation, not more preparation.

View File

@ -0,0 +1,252 @@
# Spec 337 - Evidence / Review Pack State Contract
Status: prepared
Created: 2026-05-30
Scope: Evidence / Review Pack readiness surfaces
This contract defines what the first screen must show per repo-backed state, without inventing evidence, customer-safe, auditor-ready, or export truth.
## Flow Steps
The Evidence readiness flow uses these fixed steps:
1. Source data selected
2. Evidence snapshot
3. Stored report
4. Review pack
5. Customer-safe output
6. Export / delivery
## Presentation Vocabulary
Presentation states are not a new enum family. They are labels derived from existing model state.
- `Available`
- `Missing`
- `Required`
- `Generating`
- `Failed`
- `Stale`
- `Needs review`
- `Ready`
- `Not ready`
- `Generated`
- `Unavailable`
- `Collapsed`
## Universal Defaults
- Diagnostics default: `Collapsed`.
- Raw JSON / raw payload default: hidden.
- Primary next action: exactly one per state.
- Customer-safe output: `Ready` only when Customer Review Workspace / Environment Review package readiness is repo-backed.
- Export/download: `Available` only when a ready, non-expired review pack has file metadata and the user can access the signed download.
- Operation proof: proof of generation/export is not the same as evidence output or customer-safe readiness.
- External delivery: unavailable unless a repo-backed mechanism is discovered.
## State Contracts
### 1. No Evidence Snapshot
| Field | Contract |
|---|---|
| State | No evidence snapshot |
| Visible status | Evidence snapshot required |
| Reason | No evidence snapshot is available for the selected review scope. |
| Impact | Review pack output cannot be trusted or exported yet. |
| Primary next action | Generate evidence snapshot, only when repo-supported and authorized. Otherwise show unavailable state. |
| Flow gate states | Source data selected: Available or Unavailable (repo-backed); Evidence snapshot: Missing; Stored report: Unavailable; Review pack: Unavailable; Customer-safe output: Not ready; Export / delivery: Unavailable |
| Evidence proof | Snapshot unavailable; source data proof only if repo-backed. |
| Customer-safe state | Not ready. |
| Export state | Unavailable. |
| Diagnostics default | Collapsed; no raw JSON visible. |
### 2. Evidence Snapshot Generating
| Field | Contract |
|---|---|
| State | Evidence snapshot generating |
| Visible status | Evidence generation in progress |
| Reason | Evidence snapshot generation is currently running. |
| Impact | Review pack output is not final yet. |
| Primary next action | View operation progress. |
| Flow gate states | Source data selected: Available or Unavailable; Evidence snapshot: Generating; Stored report: Unavailable; Review pack: Unavailable; Customer-safe output: Not ready; Export / delivery: Unavailable |
| Evidence proof | Linked OperationRun visible when authorized. |
| Customer-safe state | Not ready. |
| Export state | Unavailable. |
| Diagnostics default | Collapsed. |
### 3. Evidence Snapshot Failed
| Field | Contract |
|---|---|
| State | Evidence snapshot failed |
| Visible status | Evidence generation failed |
| Reason | Evidence snapshot generation ended with errors. |
| Impact | Review pack output cannot be generated from this evidence yet. |
| Primary next action | Review evidence operation. |
| Flow gate states | Source data selected: Available or Unavailable; Evidence snapshot: Failed; Stored report: Unavailable; Review pack: Unavailable; Customer-safe output: Not ready; Export / delivery: Unavailable |
| Evidence proof | Failed OperationRun proof visible when authorized. |
| Customer-safe state | Not ready. |
| Export state | Unavailable. |
| Diagnostics default | Collapsed; failure summarized before diagnostics. |
### 4. Evidence Snapshot Stale
| Field | Contract |
|---|---|
| State | Evidence snapshot stale or expired |
| Visible status | Evidence refresh required |
| Reason | Evidence exists, but its freshness is outside the acceptable window or the snapshot is expired/stale. |
| Impact | Review pack output should not be treated as current until evidence is refreshed. |
| Primary next action | Refresh evidence snapshot, only when repo-supported and authorized. |
| Flow gate states | Source data selected: Available or Unavailable; Evidence snapshot: Stale; Stored report: Unavailable or Available (repo-backed); Review pack: Unavailable or Needs review; Customer-safe output: Not ready; Export / delivery: Unavailable |
| Evidence proof | Existing snapshot proof may be shown with stale/expired state. |
| Customer-safe state | Not ready or Needs review; never Ready from stale evidence alone. |
| Export state | Unavailable unless implementation proves a still-valid review-derived pack is independent and repo-backed; default unavailable. |
| Diagnostics default | Collapsed. |
### 5. Evidence Snapshot Available, Stored Report Missing
| Field | Contract |
|---|---|
| State | Evidence snapshot available / stored report missing |
| Visible status | Stored report required |
| Reason | Evidence snapshot exists, but no stored report is available for this review output. |
| Impact | Evidence is present but not yet packaged for consumption. |
| Primary next action | Generate stored report, only if repo-supported and authorized; otherwise Open evidence snapshot or show report unavailable. |
| Flow gate states | Source data selected: Available; Evidence snapshot: Available; Stored report: Missing; Review pack: Unavailable; Customer-safe output: Not ready; Export / delivery: Unavailable |
| Evidence proof | Evidence snapshot proof available. Stored report proof unavailable. |
| Customer-safe state | Not ready. |
| Export state | Unavailable. |
| Diagnostics default | Collapsed. |
### 6. Stored Report Available, Review Pack Required
| Field | Contract |
|---|---|
| State | Stored report available / review pack required |
| Visible status | Review pack required |
| Reason | Stored report exists, but a review pack has not been generated. |
| Impact | Customer-safe delivery is not ready yet. |
| Primary next action | Generate review pack, only when authorized and entitlement/evidence requirements allow it. |
| Flow gate states | Source data selected: Available; Evidence snapshot: Available; Stored report: Available; Review pack: Required; Customer-safe output: Not ready; Export / delivery: Unavailable |
| Evidence proof | Evidence snapshot and stored report proof available. |
| Customer-safe state | Not ready. |
| Export state | Unavailable. |
| Diagnostics default | Collapsed. |
### 7. Review Pack Generating
| Field | Contract |
|---|---|
| State | Review pack generating |
| Visible status | Review pack generation in progress |
| Reason | Review pack generation is currently running. |
| Impact | Customer output is not final yet. |
| Primary next action | View operation progress. |
| Flow gate states | Source data selected: Available; Evidence snapshot: Available; Stored report: Available or Unavailable (repo-backed); Review pack: Generating; Customer-safe output: Not ready; Export / delivery: Unavailable |
| Evidence proof | ReviewPack OperationRun proof visible when authorized. |
| Customer-safe state | Not ready. |
| Export state | Unavailable. |
| Diagnostics default | Collapsed. |
### 8. Review Pack Failed
| Field | Contract |
|---|---|
| State | Review pack failed |
| Visible status | Review pack generation failed |
| Reason | Review pack generation ended with errors. |
| Impact | Customer-safe output cannot be generated from this pack yet. |
| Primary next action | Review review-pack operation. |
| Flow gate states | Source data selected: Available; Evidence snapshot: Available; Stored report: Available or Unavailable (repo-backed); Review pack: Failed; Customer-safe output: Not ready; Export / delivery: Failed or Unavailable |
| Evidence proof | Failed OperationRun proof visible when authorized. |
| Customer-safe state | Not ready. |
| Export state | Failed or Unavailable, based on repo-backed artifact state. |
| Diagnostics default | Collapsed; failure summarized before diagnostics. |
### 9. Review Pack Available, Customer-Safe Output Needs Review
| Field | Contract |
|---|---|
| State | Review pack available / customer-safe review required |
| Visible status | Customer-safe review required |
| Reason | A review pack exists, but customer-safe output has not been confirmed by repo-backed review/package readiness. |
| Impact | Do not share the pack externally until it has been reviewed. |
| Primary next action | Review customer output. |
| Flow gate states | Source data selected: Available; Evidence snapshot: Available; Stored report: Available or Unavailable (repo-backed); Review pack: Available; Customer-safe output: Needs review; Export / delivery: Required, Available, or Unavailable based on pack file truth |
| Evidence proof | Review pack proof available; OperationRun proof available when linked. |
| Customer-safe state | Needs review. |
| Export state | Do not show export/share as final customer-ready unless download/package readiness is repo-backed. |
| Diagnostics default | Collapsed. |
### 10. Customer-Safe Output Ready
| Field | Contract |
|---|---|
| State | Customer-safe output ready |
| Visible status | Customer-safe output ready |
| Reason | Review pack output is available for customer/auditor consumption through a repo-backed review/package readiness path. |
| Impact | The pack can be shared or exported according to workspace policy. |
| Primary next action | Export review pack or Download export, only when authorized. |
| Flow gate states | Source data selected: Available; Evidence snapshot: Available; Stored report: Available or Unavailable (repo-backed); Review pack: Available; Customer-safe output: Ready; Export / delivery: Available, Generated, or Required based on repo-backed artifact state |
| Evidence proof | Linked snapshot/review/pack/operation proof visible when authorized. |
| Customer-safe state | Ready. |
| Export state | Available or Required from review-pack file truth. |
| Diagnostics default | Collapsed. |
### 11. Export Available
| Field | Contract |
|---|---|
| State | Export available |
| Visible status | Review pack export available |
| Reason | A generated export artifact is available. |
| Impact | Evidence package can be downloaded or shared according to capability rules. |
| Primary next action | Download export. |
| Flow gate states | Source data selected: Available; Evidence snapshot: Available; Stored report: Available or Unavailable (repo-backed); Review pack: Available; Customer-safe output: Ready or Needs review based on repo-backed customer-safe state; Export / delivery: Available |
| Evidence proof | Review pack file metadata and OperationRun proof available when authorized. |
| Customer-safe state | Ready only if repo-backed; otherwise Needs review even when download exists. |
| Export state | Available. |
| Diagnostics default | Collapsed. |
### 12. Export Unavailable / External Delivery Not Configured
| Field | Contract |
|---|---|
| State | Export unavailable |
| Visible status | Export unavailable |
| Reason | No generated export artifact is available, the pack is not ready, the file is missing/expired, or external delivery is not configured. |
| Impact | Evidence package cannot be downloaded or delivered from this surface yet. |
| Primary next action | Generate review pack, regenerate export, review operation, or no action, based on repo-backed state and authorization. |
| Flow gate states | Source data selected: repo-backed; Evidence snapshot: repo-backed; Stored report: repo-backed; Review pack: repo-backed; Customer-safe output: repo-backed; Export / delivery: Unavailable |
| Evidence proof | Show available proof rows only. |
| Customer-safe state | Ready, Needs review, Not ready, or Unavailable based on repo truth; never inferred from missing export. |
| Export state | Unavailable. |
| Diagnostics default | Collapsed. |
## Surface-Specific Notes
### Evidence Overview
- Evidence Overview can show internal evidence and artifact proof.
- Evidence Overview must not infer `Customer-safe output ready` unless it links to a repo-backed Customer Review Workspace / Environment Review current export state.
- Raw artifact inventory remains secondary.
### Customer Review Workspace
- This is the safest source for customer-safe package readiness.
- It can show customer-safe output ready only when existing review/package/download readiness methods support that claim.
- Diagnostics remain collapsed and customer-facing default content must hide raw internals.
### Review Pack Resource
- A ready review pack with file metadata supports export/download availability.
- It does not automatically support `auditor-ready` copy.
- The existing expire action remains destructive/confirmed and is not changed by this spec.
### Stored Report
- Stored report `Available` / `Missing` is repo-backed.
- Stored report `Generating` / `Failed` is not repo-backed unless implementation discovers a direct OperationRun relation or existing job/run source.

View File

@ -0,0 +1,262 @@
# Implementation Plan: Spec 337 - Evidence Path / Review Pack Product Process Flow Alignment
- Branch: `337-evidence-review-pack-product-process-flow-alignment`
- Date: 2026-05-30
- Spec: `specs/337-evidence-review-pack-product-process-flow-alignment/spec.md`
- Input: User-provided Spec 336 draft + repo inspection; reconciled to Spec 337 because repo already has Spec 336 for Baseline Compare.
## Summary
Align existing Evidence / Review Pack readiness surfaces to the shared Product Process Flow contract from Spec 332.
This is runtime UX alignment only:
- no backend evidence engine rewrite
- no report/review-pack/export engine rewrite
- no new persistence
- no new OperationRun lifecycle semantics
- no new routes, packages, migrations, queues, scheduler, storage, or env vars
The implementation should make the readiness chain visible and truthful:
`Source data selected -> Evidence snapshot -> Stored report -> Review pack -> Customer-safe output -> Export / delivery`
## Technical Context
- Language/Version: PHP 8.4.15, Laravel 12.x.
- Primary Dependencies: Filament v5 + Livewire v4, Pest v4, Tailwind v4.
- Storage: PostgreSQL; no schema change expected.
- Testing: Pest Feature/Livewire render tests + one browser smoke file.
- Validation Lanes: confidence + browser.
- Target Platform: Sail locally; Dokploy/container deployment posture unchanged.
- Project Type: Laravel monolith under `apps/platform`.
- Performance Goals: DB-only render; no Microsoft Graph/provider calls during page render.
- Constraints: no new backend engines, no new persisted readiness truth, no new queue/scheduler behavior, no fake customer-safe/auditor-ready/export-ready claims, no raw payload default view, no cross-workspace leaks.
- Scale/Scope: bounded to existing Evidence Overview, Review Pack, Stored Report/Evidence Snapshot proof links, Environment Review export proof, and Customer Review Workspace evidence path only where needed.
## UI / Surface Guardrail Plan
- **Guardrail scope**: changed existing operator-facing and customer-safe evidence surfaces.
- **Affected routes/pages/actions/states/navigation/panel/provider surfaces**:
- `/admin/evidence/overview`
- `/admin/reviews/workspace`
- `/admin/workspaces/{workspace}/environments/{environment}/review-packs`
- `/admin/review-packs/{reviewPack}/download`
- linked Evidence Snapshot, Stored Report, Environment Review, and OperationRun proof surfaces only as needed
- **No-impact class, if applicable**: N/A.
- **Native vs custom classification summary**: mixed but existing: Filament resources/pages plus existing Blade workbench sections and shared primitives.
- **Shared-family relevance**: Product Process Flow, evidence disclosure, OperationRun proof, artifact truth, customer-safe review package state.
- **State layers in scope**: page, detail, URL-query/environment filter, artifact links, signed download availability.
- **Audience modes in scope**: operator-MSP, manager, support reviewer, customer-readable review workspace.
- **Decision/diagnostic/raw hierarchy plan**: decision-first, proof second, diagnostics/raw third and collapsed.
- **Raw/support gating plan**: collapsed by default; capability-aware using existing conventions.
- **One-primary-action / duplicate-truth control**: compute one state-specific primary action; lower sections add proof, not alternate verdicts.
- **Handling modes by drift class or surface**: review-mandatory for customer-safe and auditor-facing claims; report-only for diagnostics.
- **Repository-signal treatment**: customer-safe/export signals must be repo-backed; unavailable/deferred is preferred over invented readiness.
- **Special surface test profiles**: monitoring-state-page, shared-detail-family, customer-safe consumption path.
- **Required tests or manual smoke**: feature state contract + browser smoke + screenshots.
- **Exception path and spread control**: no exception expected; follow-up spec if implementation requires new persisted lifecycle truth.
- **Active feature PR close-out entry**: Smoke Coverage.
- **UI/Productization coverage decision**: feature-local screenshots/tests required; audit docs only if route/archetype/navigation changes during implementation.
- **Coverage artifacts to update**: none expected in prep.
- **No-impact rationale**: N/A.
- **Navigation / Filament provider-panel handling**: no panel/provider registration change. Laravel 11+/12 provider registration remains `apps/platform/bootstrap/providers.php`.
- **Screenshot or page-report need**: screenshots required under `specs/337-evidence-review-pack-product-process-flow-alignment/artifacts/screenshots/`.
## Shared Pattern & System Fit
- **Cross-cutting feature marker**: yes.
- **Systems touched**:
- Spec 332 Product Process Flow system.
- Spec 329 Evidence Overview proof-first disclosure.
- Spec 326 Customer Review Workspace evidence path.
- Review Pack Resource list/detail/download.
- Evidence Snapshot Resource proof links.
- Stored Report Resource proof links.
- Environment Review current export/review pack proof.
- OperationRun proof links.
- **Shared abstractions reused**:
- Product Process Flow render conventions from Spec 332.
- `OperationRunLinks`.
- `OperationUxPresenter` where operation start/progress messaging is already delegated.
- `UiEnforcement` and policy/capability gates for actions.
- `BadgeCatalog` / `BadgeRenderer` where status-like badges are introduced or changed.
- **New abstraction introduced? why?**: none required in prep. A small `EvidenceReviewPackPresenter` or page-local view model is allowed only if it prevents scattered Blade/Page logic and remains derived-only.
- **Why the existing abstraction was sufficient or insufficient**: Product Process Flow is sufficient for readiness steps; a local presenter may be needed because readiness combines snapshots, reports, packs, review state, and operation proof without a single persisted lifecycle record.
- **Bounded deviation / spread control**: do not create a generic workflow engine or state taxonomy. Presentation-only states stay in the feature contract and tests.
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: yes, for existing proof links and generation/export state display.
- **Central contract reused**: existing OperationRun lifecycle, `OperationRunLinks`, `OperationUxPresenter`, and services/jobs that already create or update runs.
- **Delegated UX behaviors**: queued toast, operation deep link, signed artifact link, tenant/workspace-safe URL resolution, and active-run blocked messaging remain in existing services/resources.
- **Surface-owned behavior kept local**: explanation copy, readiness flow placement, proof panel ordering.
- **Queued DB-notification policy**: no change.
- **Terminal notification path**: unchanged.
- **Exception path**: none.
## Provider Boundary & Portability Fit
- **Shared provider/platform boundary touched?**: no.
- **Provider-owned seams**: N/A.
- **Platform-core seams**: evidence/report/review artifact presentation only; no provider contract changes.
- **Neutral platform terms / contracts preserved**: workspace, environment, review, evidence snapshot, stored report, review pack, customer-safe output, export/download, operation proof.
- **Retained provider-specific semantics and why**: existing report type labels such as Entra admin roles remain only where stored reports already expose them.
- **Bounded extraction or follow-up path**: none.
## Constitution Check
*GATE: Must pass before runtime work and be rechecked after implementation design.*
- Inventory-first: pass. This spec separates source data, snapshots, stored reports, review packs, customer-safe output, and export artifacts.
- Read/write separation: pass. Existing generate/export actions stay explicit, capability-gated, and OperationRun/audit-backed; no destructive action added.
- Graph contract path: pass. Page render must not call Graph/providers.
- RBAC-UX: pass. Existing policies/capabilities must continue to deny cross-workspace/environment leakage.
- Workspace isolation: pass. Canonical evidence overview keeps environment filter/context and resource routes remain workspace/environment-scoped.
- Run observability: pass. Existing long-running evidence/review-pack work remains OperationRun-backed; this spec adds proof presentation only.
- Ops-UX lifecycle: pass. No OperationRun lifecycle changes.
- Data minimization: pass. Raw payloads and diagnostics stay collapsed and capability-aware.
- Test governance: pass. Feature + browser lanes are explicit and bounded.
- Proportionality: pass. No persisted truth; only a small derived presenter if needed.
- No premature abstraction: pass. Reuse Spec 332 Product Process Flow and existing artifact/proof helpers.
- Persisted truth: pass. No new tables/entities/artifacts.
- Behavioral state: pass. Presentation states are derived and do not add lifecycle semantics.
- Shared pattern first: pass. Product Process Flow is the core reuse.
- Provider boundary: pass. No provider seam change.
- Badge semantics: pass. New/changed status-like badges must use shared badge semantics.
- Filament-native UI: pass. Use existing Filament/Blade surface conventions and shared primitives.
- Decision-first operating model: pass. Decision card and flow come before tables/raw artifacts.
- Audience-aware disclosure: pass. Customer-readable default hides raw JSON, payloads, diagnostics, fingerprints, and internal reason ownership.
- UI/Productization coverage: pass. Existing reachable surfaces changed; feature-local screenshots/tests are required.
## Test Governance Check
- **Test purpose / classification by changed surface**: Feature tests for state contract, RBAC/context, false-claim prevention; Browser smoke for rendered flow, proof panel, collapsed diagnostics, screenshots.
- **Affected validation lanes**: confidence + browser.
- **Why this lane mix is the narrowest sufficient proof**: state composition and RBAC can be proven in Feature tests; layout/readability/collapsed diagnostics require one browser smoke.
- **Narrowest proving command(s)**:
```bash
cd apps/platform
./vendor/bin/sail artisan test tests/Feature/Filament/Spec337EvidenceReviewPackProductFlowTest.php --compact
./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec337EvidenceReviewPackProductFlowSmokeTest.php --compact
```
- **Fixture / helper / factory / seed / context cost risks**: reuse existing workspace/environment/user/EvidenceSnapshot/StoredReport/ReviewPack/EnvironmentReview/OperationRun fixtures. Do not add heavy default seeds.
- **Expensive defaults or shared helper growth introduced?**: no.
- **Heavy-family additions, promotions, or visibility changes**: one explicit browser smoke file only.
- **Surface-class relief / special coverage rule**: no standard-native relief; this is a strategic/customer-safe workbench alignment.
- **Closing validation and reviewer handoff**: verify no raw JSON initial render, no false customer-safe/export/auditor-ready claims, no cross-workspace evidence leaks, and one primary action per state.
- **Budget / baseline / trend follow-up**: none expected.
- **Review-stop questions**: if a fixture needs new backend state, stop and mark the state unavailable/deferred instead of adding persistence.
- **Escalation path**: document-in-feature for unreachable states; follow-up-spec for backend readiness engine needs.
- **Active feature PR close-out entry**: Smoke Coverage.
- **Why no dedicated follow-up spec is needed**: this is a bounded Product Process Flow consumer; broader auditor artifact delivery remains a separate future spec.
## Current Repo Truth Summary (Implementation-Relevant)
- `EvidenceSnapshot` exists with status, completeness, generated/expiry timestamps, items, workspace/environment, operation, initiator, review packs, and environment reviews.
- `StoredReport` exists for permission posture and Entra admin role reports; it stores JSONB payloads and fingerprints but has no direct OperationRun relationship in inspected code.
- `ReviewPack` exists with queued/generating/ready/failed/expired status, file metadata, evidence snapshot, environment review, OperationRun, and initiator.
- `EnvironmentReview` exists with evidence snapshot, OperationRun, current export review pack, sections, status, completeness, and summary.
- `CustomerReviewWorkspace` already derives review/package/customer-safe consumption state and signed download availability from existing review and pack truth.
- `EvidenceOverview` already has a proof-first workbench and collapsed diagnostics, but does not yet render the six-step Evidence readiness flow.
- Signed review-pack downloads are repo-backed via `ReviewPackDownloadController`, signed route, policies/capabilities, ready/non-expired/file metadata checks, and audit logging.
- Global search is disabled on relevant resources inspected (`EvidenceSnapshotResource`, `ReviewPackResource`, `StoredReportResource`, `EnvironmentReviewResource`).
## Implementation Approach
### Phase 0 - Repo Truth Gate (No Runtime Edits)
1. Confirm `repo-truth-map.md` and `evidence-review-pack-state-contract.md` still match runtime code.
2. Re-inspect Evidence Overview, Customer Review Workspace, ReviewPackResource, StoredReportResource, EvidenceSnapshotResource, EnvironmentReviewResource, OperationRun links, and download controller.
3. Mark unsupported states unavailable/deferred instead of implementing new backend truth.
### Phase 1 - Presenter / Flow Model
1. If needed, create a small `EvidenceReviewPackPresenter` or page-local view model that computes:
- decision card fields
- six flow steps
- proof items
- coverage summary
- customer-safe state
- export/download state
- diagnostics state
2. Keep the presenter derived-only. No static process memoization, no new source of truth, no new enum/status family.
### Phase 2 - Evidence Overview UI Alignment
1. Add the decision card question and fields.
2. Add the Evidence readiness flow using the Product Process Flow pattern.
3. Productize the proof panel while preserving existing collapsed diagnostics.
4. Keep raw artifact tables secondary.
5. Avoid duplicate verdict/readiness blocks.
### Phase 3 - Review Pack / Customer-Safe / Export States
1. Productize review pack state from existing `ReviewPack` status and file metadata.
2. Productize export/download state only where ready, non-expired, file-backed, signed download is authorized.
3. Productize customer-safe state only where Customer Review Workspace / Environment Review package readiness supports it.
4. Show external delivery as unavailable unless implementation discovers repo-backed delivery.
### Phase 4 - RBAC / Context / Diagnostics
1. Preserve workspace/environment/review context in all action links.
2. Respect existing capability-first gates.
3. Keep non-member/cross-workspace artifacts hidden or not found.
4. Keep diagnostics collapsed and raw JSON hidden by default.
### Phase 5 - Tests
1. Add `apps/platform/tests/Feature/Filament/Spec337EvidenceReviewPackProductFlowTest.php`.
2. Cover missing evidence, report missing, review pack required, review pack available, OperationRun proof, RBAC/context, no raw JSON, and no false claims.
3. Update existing tests only where assertions are strengthened for the new contract.
### Phase 6 - Browser Smoke + Screenshots
1. Add `apps/platform/tests/Browser/Spec337EvidenceReviewPackProductFlowSmokeTest.php`.
2. Capture screenshots under `specs/337-evidence-review-pack-product-process-flow-alignment/artifacts/screenshots/`.
3. Document unreachable states rather than faking screenshots.
### Phase 7 - Hygiene + Validation
1. Run the feature and browser commands.
2. Run overlapping filters.
3. Run Pint and `git diff --check`.
4. Report deployment impact: no migrations, packages, env vars, queues, scheduler, storage, or asset changes expected.
## Project Structure
### Documentation (this feature)
```text
specs/337-evidence-review-pack-product-process-flow-alignment/
├── spec.md
├── plan.md
├── tasks.md
├── repo-truth-map.md
├── evidence-review-pack-state-contract.md
├── checklists/
│ └── requirements.md
└── artifacts/
└── screenshots/
```
### Expected Source Code Touchpoints (implementation phase only)
```text
apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php
apps/platform/resources/views/filament/pages/monitoring/evidence-overview.blade.php
apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php
apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php
apps/platform/app/Filament/Resources/ReviewPackResource.php
apps/platform/app/Filament/Resources/ReviewPackResource/Pages/ViewReviewPack.php
apps/platform/app/Filament/Resources/EnvironmentReviewResource.php
apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php
apps/platform/app/Filament/Resources/StoredReportResource.php
apps/platform/app/Support/... (only if a small derived presenter is needed)
apps/platform/tests/Feature/Filament/Spec337EvidenceReviewPackProductFlowTest.php
apps/platform/tests/Browser/Spec337EvidenceReviewPackProductFlowSmokeTest.php
```
Do not create these runtime/test files during preparation-only work. They are listed for the implementation phase.

View File

@ -0,0 +1,173 @@
# Spec 337 - Repo Truth Map
Status: prepared
Created: 2026-05-30
Branch: `337-evidence-review-pack-product-process-flow-alignment`
## Numbering Reconciliation
The user-provided candidate called this "Spec 336". The repository already has `specs/336-baseline-compare-product-process-flow-alignment/` and a matching branch for Baseline Compare Product Process Flow Alignment. To avoid overwriting a completed/in-progress spec, this Evidence / Review Pack preparation uses Spec 337.
## Truth Classifications
- `repo-verified`: confirmed in current runtime code, schema, routes, tests, or existing spec truth maps.
- `derived from existing model`: not stored as a standalone field, but safely computed from existing repo-backed data.
- `foundation-real`: foundation exists, but this spec must still productize or connect it.
- `not available`: no repo-backed source was found during preparation.
- `deferred`: intentionally out of scope for Spec 337.
## Evidence Snapshot Model / Data Source
| Data Point | Classification | Repo Source | Notes |
|---|---|---|---|
| Evidence snapshot record | repo-verified | `evidence_snapshots`, `App\Models\EvidenceSnapshot` | Scoped by `workspace_id` and `managed_environment_id`. |
| Evidence snapshot status | repo-verified | `App\Enums\EvidenceSnapshotStatus` | `queued`, `generating`, `active`, `superseded`, `expired`, `failed`. |
| Evidence completeness | repo-verified | `App\Enums\EvidenceCompletenessState`, `evidence_snapshots.completeness_state` | `complete`, `partial`, `missing`, `stale`. |
| Evidence freshness | repo-verified | `evidence_snapshots.generated_at`, `expires_at`; `evidence_snapshot_items.freshness_at` | Use expiry/completeness/item freshness where available. |
| Evidence operation proof | repo-verified | `evidence_snapshots.operation_run_id`, `EvidenceSnapshot::operationRun()` | Generation creates an `OperationRun` through `EvidenceSnapshotService` / `GenerateEvidenceSnapshotJob`. |
| Evidence initiator | repo-verified | `evidence_snapshots.initiated_by_user_id`, `initiator()` | Useful for proof panel. |
| Evidence snapshot items | repo-verified | `evidence_snapshot_items`, `EvidenceSnapshot::items()` | Dimension/state/source summaries. |
| Active/current evidence | repo-verified | model scopes `active()`, `current()` and partial unique index | Current active snapshot is repo-backed. |
| Raw evidence payload | repo-verified | `evidence_snapshot_items.summary_payload`, snapshot `summary` | Must remain collapsed by default. |
## Stored Report Model / Data Source
| Data Point | Classification | Repo Source | Notes |
|---|---|---|---|
| Stored report record | repo-verified | `stored_reports`, `App\Models\StoredReport` | Scoped by `workspace_id`, `managed_environment_id`, `report_type`. |
| Stored report payload | repo-verified | `stored_reports.payload` JSONB | Must not be default-visible on customer-safe surfaces. |
| Stored report type | repo-verified | `StoredReport::TYPE_PERMISSION_POSTURE`, `TYPE_ENTRA_ADMIN_ROLES` | Only these inspected report types are supported. |
| Stored report fingerprint | repo-verified | `fingerprint`, `previous_fingerprint` | Artifact truth, not customer-safe readiness by itself. |
| Stored report freshness | derived from existing model | `created_at`, `updated_at`, `valid_from`, `valid_until` | Use only where the implementation already treats report validity as meaningful. |
| Stored report generation OperationRun | not available | no direct relation found on `StoredReport` | Do not show generating/failed report states unless a repo-backed run source is discovered in implementation. |
| Stored report global search | repo-verified | `StoredReportResource::$isGloballySearchable = false` | Filament global search hard rule is satisfied by disabling search. |
## Review Pack Model / Data Source
| Data Point | Classification | Repo Source | Notes |
|---|---|---|---|
| Review pack record | repo-verified | `review_packs`, `App\Models\ReviewPack` | Scoped by workspace and environment. |
| Review pack status | repo-verified | `App\Enums\ReviewPackStatus` | `queued`, `generating`, `ready`, `failed`, `expired`. |
| Review pack operation proof | repo-verified | `review_packs.operation_run_id`, `ReviewPack::operationRun()` | Generation uses `ReviewPackService` / `GenerateReviewPackJob`. |
| Review pack initiator | repo-verified | `initiated_by_user_id`, `initiator()` | Useful for proof panel. |
| Review pack evidence snapshot relation | repo-verified | `review_packs.evidence_snapshot_id`, `ReviewPack::evidenceSnapshot()` | Review pack can be anchored to snapshot. |
| Review pack environment review relation | repo-verified | `review_packs.environment_review_id`, `ReviewPack::environmentReview()` | Review-derived export packs are repo-backed. |
| Review pack file artifact | repo-verified | `file_disk`, `file_path`, `file_size`, `sha256` | Required for download/export readiness. |
| Review pack expiry | repo-verified | `expires_at`, `ReviewPack::expired()` | Expired pack is not export-ready. |
| Review pack summary/options | repo-verified | JSON casts on `summary`, `options` | Coverage values must come from these or related review/evidence records only. |
| Review pack global search | repo-verified | `ReviewPackResource::$isGloballySearchable = false` | Filament global search hard rule is satisfied by disabling search. |
## Tenant / Environment Review Relationship
| Data Point | Classification | Repo Source | Notes |
|---|---|---|---|
| Environment review record | repo-verified | `environment_reviews`, `App\Models\EnvironmentReview` | Scoped by workspace and environment. |
| Review status | repo-verified | `App\Enums\EnvironmentReviewStatus` | `draft`, `ready`, `published`, `archived`, `superseded`, `failed`. |
| Review completeness | repo-verified | `App\Enums\EnvironmentReviewCompletenessState` | `complete`, `partial`, `missing`, `stale`. |
| Review evidence snapshot | repo-verified | `environment_reviews.evidence_snapshot_id`, `evidenceSnapshot()` | Review is anchored to evidence snapshot. |
| Review current export pack | repo-verified | `current_export_review_pack_id`, `currentExportReviewPack()` | This is the primary export/customer package relation. |
| Review operation proof | repo-verified | `operation_run_id`, `operationRun()` | Review generation proof exists. |
| Review sections | repo-verified | `EnvironmentReview::sections()` | Coverage/content summary may be derived only from these summaries. |
| Customer-safe package summary | derived from existing model | `EnvironmentReview.summary`, `currentExportReviewPack`, Customer Review Workspace readiness methods | No separate persisted `customer_safe` flag found. |
| EnvironmentReview global search | repo-verified | `EnvironmentReviewResource::$isGloballySearchable = false` | Filament global search hard rule is satisfied by disabling search. |
## Customer Review Workspace Relationship
| Data Point | Classification | Repo Source | Notes |
|---|---|---|---|
| Customer Review Workspace route | repo-verified | `/admin/reviews/workspace`, `CustomerReviewWorkspace` | Existing customer-safe consumption surface. |
| Latest review package payload | repo-verified | `CustomerReviewWorkspace::latestReviewConsumptionPayload()` | Loads review, current export pack, evidence snapshot, OperationRuns. |
| Evidence path panel | repo-verified | `CustomerReviewWorkspace::evidencePathForReview()` and Blade view | Already separates evidence path/proof rows. |
| Review pack panel | repo-verified | `CustomerReviewWorkspace::reviewPackPanelForReview()` | Shows review pack state and export proof. |
| Customer-safe readiness | derived from existing model | `reviewReadinessForTenant()`, `governancePackageAvailability()`, `workspaceReviewNeedsAttention()` | Ready/shareable state is derived from published review, evidence/package availability, accepted risk follow-up, and download URL. |
| Download URL for ready pack | repo-verified | `reviewPackDownloadUrl()` | Requires ready package state, user capability, non-expired pack, file path/disk. |
| Diagnostics collapsed | repo-verified | `diagnosticsDisclosureForReview()` and view details block | Keep collapsed by default. |
| Separate public delivery/email/share | not available | no delivery mechanism found | External delivery must render unavailable/deferred. |
## OperationRun Relationship For Generation / Export
| Artifact / Flow | Classification | Repo Source | Notes |
|---|---|---|---|
| Evidence snapshot generation run | repo-verified | `EvidenceSnapshotService::generate()`, `GenerateEvidenceSnapshotJob` | Creates/updates linked `OperationRun`. |
| Review pack generation run | repo-verified | `ReviewPackService::generate()`, `GenerateReviewPackJob` | Creates/updates linked `OperationRun`. |
| Review-derived export generation run | repo-verified | `ReviewPackService::generateFromReview()` | Links `ReviewPack` to `EnvironmentReview` and `OperationRun`. |
| Environment review generation run | repo-verified | `EnvironmentReview::operationRun()` and resource/service usage | Review proof source exists. |
| Stored report generation run | not available | no direct `StoredReport::operationRun()` relation found | Do not invent report generating/failed OperationRun proof unless discovered later. |
| Operation status/outcome | repo-verified | `OperationRunStatus`, `OperationRunOutcome` | Use status/outcome/timeline/initiator/type/result in proof panel. |
| Cross-workspace OperationRun visibility | repo-verified | policies/helpers and route scoping | Must remain enforced in tests. |
## Export / Download Artifact Relationship
| Data Point | Classification | Repo Source | Notes |
|---|---|---|---|
| Signed download route | repo-verified | `/admin/review-packs/{reviewPack}/download`, `ReviewPackDownloadController`, route name `admin.review-packs.download` | Signed URL used by service/resource/workspace. |
| Download authorization | repo-verified | controller checks user, tenant access, `Capabilities::REVIEW_PACK_VIEW` | Preserve. |
| Ready/exportable pack | repo-verified | ready status, not expired, file exists via disk/path | Required for `Export available`. |
| Download audit | repo-verified | `ReviewPackDownloaded` audit in controller | Proof/audit exists. |
| Missing file behavior | repo-verified | controller aborts 404 when not ready/expired/missing file | Do not surface as available. |
| External delivery | not available | no email/share/portal delivery source found | Render `External delivery is not configured` if needed. |
## Evidence Freshness Source
| Freshness Signal | Classification | Repo Source | Notes |
|---|---|---|---|
| Snapshot generated timestamp | repo-verified | `EvidenceSnapshot.generated_at` | Displayable proof. |
| Snapshot expiry | repo-verified | `EvidenceSnapshot.expires_at` | Use for stale/expired/unavailable. |
| Snapshot completeness | repo-verified | `EvidenceSnapshot.completeness_state` | Complete/partial/missing/stale. |
| Item freshness | repo-verified | `EvidenceSnapshotItem.freshness_at`, `measured_at` | Use only when summarized or safe to show. |
| Stored report validity | derived from existing model | `valid_from`, `valid_until` | No automatic readiness claim unless existing UI/service treats validity as active. |
## Customer-Safe State Source
| State Source | Classification | Repo Source | Notes |
|---|---|---|---|
| Explicit persisted customer-safe flag | not available | no standalone field found | Do not add or pretend one exists. |
| Customer Review Workspace readiness | derived from existing model | `reviewReadinessForTenant()`, `governancePackageAvailability()` | Safest source for "ready to share" style presentation. |
| Review current export pack | repo-verified | `EnvironmentReview.current_export_review_pack_id` | Indicates generated package linked to review. |
| Review accepted-risk follow-up | derived from existing model | Customer Review Workspace methods and review summary | Can require review before sharing. |
| Evidence Overview customer-safe state | foundation-real | Evidence Overview can link artifacts but does not by itself confirm customer-safe output | Render unavailable/needs review unless Customer Review Workspace package readiness is linked. |
## RBAC / Capabilities
| Capability / Check | Classification | Repo Source | Notes |
|---|---|---|---|
| Evidence view/manage | repo-verified | `EvidenceSnapshotPolicy`, `EvidenceSnapshotResource`, capabilities | Generate/refresh/expire evidence actions are capability-gated. |
| Review pack view/manage | repo-verified | `ReviewPackPolicy`, `ReviewPackResource`, download controller | Generate/download/expire/regenerate are gated. |
| Environment review view/manage/export | repo-verified | `EnvironmentReviewPolicy`, `EnvironmentReviewResource` | Export is policy/capability-backed. |
| Stored report view | repo-verified | `StoredReportResource` and report capability rules | Read-only report surface. |
| OperationRun proof access | repo-verified | `OperationRunLinks`, resource/link visibility helpers | Proof links must stay authorized. |
| Diagnostics access | foundation-real | existing collapsed diagnostics sections | Must follow existing capability/disclosure conventions. |
## Routes / Surfaces
| Surface | Classification | Repo Source | Notes |
|---|---|---|---|
| Evidence Overview | repo-verified | `apps/platform/routes/web.php`, `EvidenceOverview` | `/admin/evidence/overview`; named `admin.evidence.overview`. |
| Customer Review Workspace | repo-verified | route list, `CustomerReviewWorkspace`, panel provider registration | `/admin/reviews/workspace`. |
| Review Pack list/detail | repo-verified | route list, `ReviewPackResource` | Environment-owned route under workspace/environment. |
| Review Pack download | repo-verified | route list, `ReviewPackDownloadController` | Signed route. |
| Evidence Snapshot resource | repo-verified | `EvidenceSnapshotResource` | Global search disabled; list/view pages exist. |
| Stored Report resource | repo-verified | `StoredReportResource` | Global search disabled; read-only detail. |
| Environment Review resource | repo-verified | `EnvironmentReviewResource` | Global search disabled; export action exists. |
## Existing Tests
| Test Area | Classification | Repo Source | Notes |
|---|---|---|---|
| Evidence Overview | repo-verified | `apps/platform/tests/Feature/Evidence/EvidenceOverviewPageTest.php`, `EvidenceOverviewWorkspaceHubContractTest.php`, `Spec329EvidenceAuditDisclosureProductizationTest.php` | Existing evidence disclosure behavior. |
| Evidence Snapshot | repo-verified | `apps/platform/tests/Feature/Evidence/*`, `apps/platform/tests/Unit/Evidence/*` | Snapshot generation/resolver/completeness coverage. |
| Stored Reports | repo-verified | `apps/platform/tests/Feature/StoredReports/*`, `apps/platform/tests/Feature/Artifacts/*` | Stored report source/detail/entitlement tests. |
| Review Packs | repo-verified | `apps/platform/tests/Feature/ReviewPack/*`, `ReviewPackAccessBoundaryTest.php` | Generation, download, RBAC, widget, redaction, entitlement. |
| Customer Review Workspace | repo-verified | `apps/platform/tests/Feature/Reviews/*`, `apps/platform/tests/Browser/Reviews/CustomerReviewWorkspaceSmokeTest.php`, `Spec326CustomerReviewWorkspaceProductizationSmokeTest.php` | Existing customer workspace proof/package behavior. |
| Product Process Flow | repo-verified | `apps/platform/tests/Feature/Filament/Spec332ProductProcessFlowSystemTest.php`, browser Spec 332 tests | Shared pattern foundation. |
| Baseline Product Flow consumer | repo-verified | `apps/platform/tests/Feature/Filament/Spec336BaselineCompareProductProcessFlowAlignmentTest.php`, browser Spec 336 test | Completed/adjacent consumer pattern. |
| Spec 337 tests | deferred | planned in `tasks.md` | Not created during prep-only work. |
## Productization Implications
- Evidence Overview is the primary place to add the six-step flow.
- Customer Review Workspace is the safest source for customer-safe shareability; Evidence Overview must not infer it from raw evidence alone.
- Review Pack `ready` plus file metadata enables download/export availability, but does not automatically mean auditor-ready.
- StoredReport can be `Available` or `Missing`; generating/failed report states are not repo-backed unless implementation discovers a valid OperationRun relation.
- External delivery is not repo-backed and must be shown as unavailable/deferred if displayed.
- Raw payloads and diagnostics already have collapsed patterns; Spec 337 must preserve and test that behavior.

View File

@ -0,0 +1,693 @@
# Feature Specification: Spec 337 - Evidence Path / Review Pack Product Process Flow Alignment
**Feature Branch**: `337-evidence-review-pack-product-process-flow-alignment`
**Created**: 2026-05-30
**Status**: Draft
**Type**: Runtime UX alignment / Product Process Flow consumer / Evidence and Review Pack productization
**Runtime posture**: Reuse existing Spec 332 Product Process Flow system. No backend evidence, report, review-pack, export, queue, or storage rewrite. No new persisted truth.
**Input**: User-provided Spec 336 draft, repo-truth reconciled to Spec 337 because `336-baseline-compare-product-process-flow-alignment` already exists in this repo.
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
- **Problem**: TenantPilot has repo-backed evidence snapshots, stored reports, review packs, customer review workspace signals, OperationRun proof, and signed downloads, but the path from technical evidence to customer- or auditor-consumable output is not expressed as one decision-first readiness flow.
- **Today's failure**: Operators must infer readiness from raw artifact lists, internal report names, timestamps, and generated files instead of seeing what exists, what is missing, what is stale, what is customer-safe, what can be exported, and the next action.
- **User-visible improvement**: Evidence and Review Pack surfaces answer in the first screen: Status, Reason, Impact, Primary next action, Evidence readiness flow, Available proof, Customer-safe output state, Export/download state, and collapsed diagnostics.
- **Smallest enterprise-capable version**: Reuse Spec 332's Product Process Flow pattern on existing Evidence Overview, Review Pack, Stored Report, and Customer Review Workspace evidence sections without changing generation engines or persistence.
- **Explicit non-goals**: New evidence backend, report engine, review-pack engine, storage backend, export format, PDF/ZIP generation logic, OperationRun model changes, migrations, packages, queue/scheduler changes, Customer Review Workspace redesign, Baseline Compare changes, Restore changes, billing/entitlement changes, generic document management.
- **Permanent complexity imported**: A small page/surface presenter only if needed, a feature-local state contract, focused Feature tests, one browser smoke file, and screenshot artifacts. No new tables, enums, status family, or platform workflow framework.
- **Why now**: Restore and Baseline Compare now consume the Product Process Flow pattern; Evidence/Review Packs are the next high-value consumer because they are the customer-safe/auditor-facing continuation of proof and review workflows.
- **Why not local**: Copy-only fixes would leave readiness, proof, customer-safe output, and export states scattered across evidence and review surfaces. A shared flow contract prevents another local readiness dialect.
- **Approval class**: Core Enterprise
- **Red flags triggered**: Customer-safe trust language, audit/evidence disclosure, reachable UI surface change, cross-workspace leakage risk. Defense: use repo-backed states only, collapse raw diagnostics, preserve existing RBAC/policies, and test false-claim prevention.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 1 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 10/12**
- **Decision**: approve
## Spec Scope Fields *(mandatory)*
- **Scope**: canonical-view plus tenant/environment-owned artifacts.
- **Primary Routes**:
- Evidence Overview: `/admin/evidence/overview` (`admin.evidence.overview`, `App\Filament\Pages\Monitoring\EvidenceOverview`)
- Customer Review Workspace evidence path: `/admin/reviews/workspace` (`App\Filament\Pages\Reviews\CustomerReviewWorkspace`), only if needed for alignment
- Review Pack list/detail: `/admin/workspaces/{workspace}/environments/{environment}/review-packs`
- Review Pack signed download: `/admin/review-packs/{reviewPack}/download` (`admin.review-packs.download`)
- Evidence Snapshot and Stored Report detail surfaces, only as linked proof surfaces
- Environment Review detail/export state, only if needed for review-pack readiness truth
- **Data Ownership**:
- Workspace-owned and environment-owned: `EvidenceSnapshot`, `EvidenceSnapshotItem`, `StoredReport`, `ReviewPack`, `EnvironmentReview`, `EnvironmentReviewSection`, `OperationRun`.
- Signed download/export artifact truth is stored on `ReviewPack.file_disk`, `file_path`, `file_size`, `sha256`, `generated_at`, `expires_at`, and status.
- Customer-safe readiness is derived from existing Customer Review Workspace review/package readiness; no separate `customer_safe` persisted flag is introduced.
- **RBAC**:
- Workspace membership and managed environment access remain mandatory.
- Existing capabilities gate actions and links: `EVIDENCE_VIEW`, `EVIDENCE_MANAGE`, `REVIEW_PACK_VIEW`, `REVIEW_PACK_MANAGE`, `ENVIRONMENT_REVIEW_VIEW`, `ENVIRONMENT_REVIEW_MANAGE`, report-specific capabilities, and OperationRun view capability.
- Non-member or cross-workspace access remains not-found; member-but-missing-capability follows existing hidden/disabled/403 conventions.
For canonical-view specs, the spec MUST define:
- **Default filter behavior when tenant-context is active**: preserve current environment filter behavior. Evidence Overview must preselect or preserve the active managed environment when provided, otherwise show authorized workspace-scoped evidence without leaking artifacts from inaccessible environments.
- **Explicit entitlement checks preventing cross-tenant leakage**: all artifact links and generated actions must continue to use existing policies/capability checks and route-model scoping. No `/admin/t` routes or legacy tenant query aliases may be introduced.
## UI Surface Impact *(mandatory - UI-COV-001)*
Does this spec add, remove, rename, or materially change any reachable UI surface?
- [ ] No UI surface impact
- [x] Existing page changed
- [ ] New page/route added
- [ ] Navigation changed
- [ ] Filament panel/provider surface changed
- [ ] New modal/drawer/wizard/action added
- [x] New table/form/state added
- [x] Customer-facing surface changed
- [ ] Dangerous action changed
- [x] Status/evidence/review presentation changed
- [x] Workspace/environment context presentation changed
If any box except "No UI surface impact" is checked, complete the UI/Productization Coverage section below. "No UI surface impact" MUST NOT be checked together with another impact box; if a guarded file path changes for non-surface reasons, keep only "No UI surface impact" checked and explain the rationale below.
## UI/Productization Coverage *(mandatory when UI Surface Impact is not "No UI surface impact"; otherwise write `N/A - no reachable UI surface impact` plus rationale)*
- **Route/page/surface**: Evidence Overview (`App\Filament\Pages\Monitoring\EvidenceOverview`), Customer Review Workspace evidence path (`App\Filament\Pages\Reviews\CustomerReviewWorkspace`, only if needed), Review Pack Resource list/detail, and linked proof surfaces.
- **Current or new page archetype**: Existing evidence/report/review workbench and customer review workspace surfaces; archetypes unchanged.
- **Design depth**: Strategic Surface for Evidence Overview and Customer Review Workspace; Domain Pattern Surface for Review Pack/Stored Report/Evidence Snapshot detail proof.
- **Repo-truth level**: repo-verified (see `repo-truth-map.md`).
- **Existing pattern reused**: Spec 332 Product Process Flow system, Spec 329 evidence disclosure/proof-first workbench, Spec 326 Customer Review Workspace decision/readiness sections, existing OperationRun proof links.
- **New pattern required**: none. A small feature presenter is allowed only if it centralizes repo-backed state mapping and does not become a generic workflow engine.
- **Screenshot required**: yes, under `specs/337-evidence-review-pack-product-process-flow-alignment/artifacts/screenshots/`.
- **Page audit required**: no by default; route/archetype unchanged. Feature-local screenshots and tests are required proof. If implementation changes navigation/archetype, update the UI audit artifacts in the implementation PR.
- **Customer-safe review required**: yes. This spec touches customer/auditor consumption language and must not claim customer-safe, auditor-ready, export-ready, or complete unless repo-backed.
- **Dangerous-action review required**: no destructive action change. Existing generate/export/download/expire semantics remain governed by current actions, confirmations where already required, policies, and audits.
- **Coverage files updated or explicitly not needed**:
- [ ] `docs/ui-ux-enterprise-audit/route-inventory.md`
- [ ] `docs/ui-ux-enterprise-audit/design-coverage-matrix.md`
- [ ] `docs/ui-ux-enterprise-audit/page-reports/...`
- [ ] `docs/ui-ux-enterprise-audit/strategic-surfaces.md`
- [ ] `docs/ui-ux-enterprise-audit/grouped-follow-up-candidates.md`
- [ ] `docs/ui-ux-enterprise-audit/unresolved-pages.md`
- [ ] `N/A - no reachable UI surface impact`
- **No-impact rationale when applicable**: N/A. UI impact exists; coverage artifacts may remain unchanged only if implementation preserves route family, navigation, and archetype.
## Cross-Cutting / Shared Pattern Reuse *(mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write `N/A - no shared interaction family touched`)*
- **Cross-cutting feature?**: yes
- **Interaction class(es)**: decision cards, status messaging, Product Process Flow steps, action links, OperationRun proof links, evidence/report/review artifact viewers, collapsed diagnostics.
- **Systems touched**: Spec 332 Product Process Flow system; Evidence Overview proof workbench; Customer Review Workspace evidence path; Review Pack Resource; Stored Report Resource; Evidence Snapshot Resource; OperationRun link helpers.
- **Existing pattern(s) to extend**: Spec 332 process flow, Spec 329 proof-first evidence disclosure, Spec 326 customer review readiness, `OperationRunLinks`, `OperationUxPresenter`, `UiEnforcement`, `BadgeCatalog` / `BadgeRenderer`.
- **Shared contract / presenter / builder / renderer to reuse**: Product Process Flow render conventions from Spec 332. Use existing artifact and badge presenters where available.
- **Why the existing shared path is sufficient or insufficient**: sufficient for step-based readiness and proof separation; insufficient only if current components cannot express the six evidence-chain steps, in which case a bounded renderer extension is allowed.
- **Allowed deviation and why**: no new framework. Feature-local mapping may exist because evidence readiness combines several existing artifacts without a persisted lifecycle table.
- **Consistency impact**: Restore, Baseline Compare, Evidence, and Review Packs use the same Status/Reason/Impact/Next action order, honest unavailable/deferred states, and collapsed diagnostics.
- **Review focus**: no fake customer-safe/auditor-ready/export-ready claims, no duplicate readiness blocks, no raw JSON by default, no cross-workspace links.
## OperationRun UX Impact *(mandatory when the feature creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`; otherwise write `N/A - no OperationRun start or link semantics touched`)*
- **Touches OperationRun start/completion/link UX?**: yes, by presenting existing evidence/report/review-pack generation proof and operation deep links.
- **Shared OperationRun UX contract/layer reused**: existing `OperationRunLinks`, `OperationUxPresenter`, `OperationRunService`, and current service/job operation lifecycle.
- **Delegated start/completion UX behaviors**: existing queued toast, "View operation" links, operation status/outcome, and signed artifact links remain delegated to current services and helpers.
- **Local surface-owned behavior that remains**: state-specific explanation copy and artifact proof placement.
- **Queued DB-notification policy**: no change; no new queued DB notification.
- **Terminal notification path**: unchanged.
- **Exception required?**: none.
## Provider Boundary / Platform Core Check *(mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write `N/A - no shared provider/platform boundary touched`)*
- **Shared provider/platform boundary touched?**: no
- **Boundary classification**: N/A
- **Seams affected**: N/A
- **Neutral platform terms preserved or introduced**: workspace, environment, review, evidence snapshot, stored report, review pack, customer-safe output, export/download.
- **Provider-specific semantics retained and why**: existing report labels such as Entra admin roles may remain only where repo-backed stored reports already expose them.
- **Why this does not deepen provider coupling accidentally**: the flow is artifact/process based and does not add provider contracts or provider-specific truth.
- **Follow-up path**: none.
## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)*
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|---|---|---|---|---|---|---|
| Evidence Overview: decision card, readiness flow, proof panel, collapsed diagnostics | yes | Native Filament + shared primitives + existing Blade workbench | Product Process Flow, evidence disclosure, OperationRun proof | page, table context, URL query | no | Strategic surface, bounded alignment |
| Customer Review Workspace evidence path: truthful customer-safe/export state alignment, if needed | yes | Native Filament + existing workspace Blade workbench | customer-safe readiness, review pack package state | page, URL query | no | Customer-facing consumption path; only adjust if needed |
| Review Pack Resource list/detail: readiness/export proof copy or placement, if needed | yes | Native Filament resource | review pack artifact proof, signed download | list/detail/header actions | no | No action semantics change |
## Summary
Align the Evidence Path and Review Pack experience with the reusable Product Process Flow system introduced in Spec 332.
TenantPilot already has foundations for:
- evidence snapshots
- stored reports
- review packs
- tenant/environment reviews
- customer review workspace
- audit log and OperationRun proof
- report export/download
Spec 337 productizes the visible chain:
1. Source data selected
2. Evidence snapshot
3. Stored report
4. Review pack
5. Customer-safe output
6. Export / delivery
The first screen must answer: is this evidence package ready for customer or auditor consumption?
The answer must include:
- Status
- Reason
- Impact
- Primary next action
- Evidence readiness flow
- Available proof
- Customer-safe output state
- Export/download state
- Diagnostics collapsed
## Repo-Truth First Requirement
Before runtime changes, the spec requires:
- `specs/337-evidence-review-pack-product-process-flow-alignment/repo-truth-map.md`
- `specs/337-evidence-review-pack-product-process-flow-alignment/evidence-review-pack-state-contract.md`
These files classify every data point as `repo-verified`, `derived from existing model`, `foundation-real`, `not available`, or `deferred`. No unavailable evidence, report, export, or customer-safe state may be invented.
## Scope
### In Scope
- Evidence Overview readiness/product-flow alignment.
- Review Pack generation/status/readiness presentation.
- Stored Report and Evidence Snapshot proof/readiness links.
- Customer Review Workspace evidence path section only if needed to preserve the same flow language.
- Review Pack export/download readiness.
- OperationRun proof for evidence/report/review-pack generation and export.
- Diagnostics collapsed by default.
- Spec 337 feature tests, browser smoke, and screenshots.
- Spec 337 state contract and repo-truth artifacts.
### Out of Scope
- New evidence snapshot backend.
- New report generation engine.
- New review pack engine.
- New storage backend.
- New export format.
- New PDF/ZIP generation logic.
- New OperationRun model changes.
- New migrations unless a later implementation proves they are strictly required and receives explicit review.
- New packages.
- New queue/scheduler changes.
- Customer Review Workspace redesign.
- Baseline Compare changes.
- Restore changes.
- Billing/entitlement changes.
- Generic document management.
## Product Principles
- Evidence-first.
- Customer-safe consumption.
- Decision-first.
- Auditability.
- OperationRun proof-aware.
- Workspace/environment isolation.
- Diagnostics collapsed.
- Raw artifact metadata secondary.
- No false customer-safe claim.
- No false auditor-ready claim.
- No false export-ready claim.
Avoid raw stored-report tables as the main UX, raw evidence IDs as primary labels, export file dumps, technical payload default views, generic readiness claims, customer-facing raw diagnostics, duplicated readiness blocks, and nested card-heavy layouts.
## Required Product Flow
Evidence / Review Pack uses the Product Process Flow component.
Default flow steps:
1. Source data selected
2. Evidence snapshot
3. Stored report
4. Review pack
5. Customer-safe output
6. Export / delivery
If the repo only supports part of the chain for a surface, unsupported steps render as unavailable or deferred with honest copy.
### Step Semantics
- **Source data selected**: `Available`, `Missing`, `Stale`, `Unavailable`
- **Evidence snapshot**: `Available`, `Missing`, `Generating`, `Failed`, `Stale`, `Unavailable`
- **Stored report**: `Available`, `Missing`, `Generating`, `Failed`, `Unavailable`
- **Review pack**: `Available`, `Required`, `Generating`, `Failed`, `Unavailable`
- **Customer-safe output**: `Ready`, `Needs review`, `Not ready`, `Unavailable`
- **Export / delivery**: `Available`, `Required`, `Generated`, `Failed`, `Unavailable`
Customer-safe output MUST NOT show `Ready` unless repo truth supports it for the selected surface. Evidence Overview alone does not create a customer-safe state.
## Evidence / Review Pack Product States
At minimum, support the following state family where fixtures are repo-backed:
- Evidence snapshot required.
- Evidence generation in progress.
- Evidence generation failed.
- Stored report required.
- Review pack required.
- Review pack generation in progress.
- Customer-safe review required.
- Customer-safe output ready.
- Review pack export/download available.
- Export unavailable or external delivery not configured.
The exact copy and flow gate states are defined in `evidence-review-pack-state-contract.md`.
## Page Layout
Use a calm main/aside layout where practical.
Default first screen:
- Main: decision card, Evidence readiness flow, readiness summary, review-pack contents/coverage, customer-safe output state.
- Aside: evidence proof, evidence snapshot, stored report, review pack, OperationRun proof, export artifact, diagnostics collapsed.
If an aside convention does not exist on the page, proof state may sit below the decision card but above raw artifact lists.
## Decision Card
Required question:
> Is this evidence package ready for customer or auditor consumption?
Required fields:
- Status
- Reason
- Impact
- Primary next action
One state-specific primary action is visible. Secondary proof/export links remain secondary.
## Evidence Readiness Flow
Expected title: `Evidence readiness flow`
Expected subtitle: `Customer-safe evidence requires source data, evidence snapshot, stored report, review pack, and export readiness.`
Rules:
- Use horizontal variant by default if space allows.
- Use vertical variant if the page layout requires stacked flow.
- No duplicate lower readiness blocks.
- No raw artifact placeholders.
- No fake progress.
- No clipped status badges.
- Diagnostics collapsed by default.
## Evidence Proof Panel
Create or productize an Evidence Proof panel with these rows:
- Source data
- Evidence snapshot
- Stored report
- Review pack
- Operation proof
- Export artifact
- Customer-safe state
- Diagnostics
Allowed presentation states: `Available`, `Unavailable`, `Generating`, `Failed`, `Stale`, `Needs review`, `Ready`, `Not ready`, `Collapsed`.
Do not claim `Customer-safe`, `Auditor-ready`, `Export-ready`, or `Complete` unless repo truth supports it.
## Review Pack Contents / Coverage
If repo-backed, show a compact coverage summary such as:
- Controls covered
- Findings included
- Accepted risks included
- Evidence snapshots included
- Reports included
- Generated files
- Missing evidence
- Warnings
Only show metrics from repo truth. If unavailable, show: `Review pack coverage details are not available for this artifact.`
## Customer-Safe Output
Separate internal evidence from customer-safe output.
Allowed states:
- Customer-safe output ready
- Customer-safe review required
- Customer-safe output unavailable
- Foundation exists, but customer-safe state is not linked
Customer-safe ready is only allowed when the Customer Review Workspace / Environment Review / current export pack path has a repo-backed ready package and no unresolved readiness blocker for that surface.
## Export / Delivery
Show export/download state when repo-backed.
Allowed states:
- Export available
- Export required
- Export generating
- Export failed
- Export unavailable
Actions appear only if authorized:
- Download export
- Regenerate export
- View operation
- Open stored report
External delivery is not configured unless a repo-backed mechanism is discovered in implementation. Do not fake email, PSA, sharing, or portal delivery.
## OperationRun Proof
Evidence/report/review-pack generation must show OperationRun proof when available:
- Operation status
- Started at
- Completed at
- Requested by
- Run type
- Result
- Link to operation detail
If unavailable, show: `Operation proof unavailable. No generation operation is linked to this artifact.`
## Diagnostics
Diagnostics are collapsed by default.
Allowed collapsed sections:
- Raw report metadata
- Raw evidence payload
- Generation diagnostics
- Export diagnostics
- Provider diagnostics
Never default-show raw JSON, stack traces, provider secrets, raw OperationRun JSON, or internal exceptions.
## Navigation / Context
Preserve workspace/environment/review context.
Allowed actions, only when route- and capability-backed:
- Generate evidence snapshot
- Open evidence snapshot
- Open stored report
- Generate review pack
- Open review pack
- Download export
- View operation proof
- Open customer review workspace
- Return to review
No hardcoded workspace IDs, `/admin/t` routes, legacy tenant aliases, or context-losing proof/export links.
## RBAC / Capabilities
Actions only appear when authorized:
- Generate evidence
- Generate report
- Generate review pack
- Export/download
- Open operation proof
- Open diagnostics
- Open customer review workspace
Unauthorized users must either not see the action or see an unavailable state if repo conventions support that. No new destructive actions are introduced.
## Required Tests
Create:
`apps/platform/tests/Feature/Filament/Spec337EvidenceReviewPackProductFlowTest.php`
Required assertions:
- Missing evidence renders the decision question, `Evidence snapshot required`, the evidence flow, missing snapshot, unavailable review pack, not-ready customer-safe output, collapsed diagnostics, and no raw JSON.
- Evidence snapshot available / report missing shows snapshot available, stored report required, and no fake review-pack ready claim when fixtures support it.
- Review pack required shows stored report available, review pack required, and the generate review pack action only when authorized.
- Review pack available shows review pack available, customer-safe state truthful, export state truthful, and no false auditor-ready claim.
- Operation proof shows linked generation OperationRun, blocks cross-workspace OperationRun leakage, and shows failed OperationRun as failed proof.
- RBAC/context tests prove unauthorized users cannot generate/export, cross-workspace evidence is not visible, and no legacy tenant alias is used.
## Browser Smoke
Create:
`apps/platform/tests/Browser/Spec337EvidenceReviewPackProductFlowSmokeTest.php`
Browser states:
- missing evidence snapshot
- evidence generating if fixture-supported
- stored report available / review pack missing
- review pack available if fixture-supported
- export unavailable
- diagnostics collapsed
- dark mode if practical
Required screenshots:
- `01-evidence-snapshot-required.png`
- `02-evidence-generating.png`
- `03-stored-report-required.png`
- `04-review-pack-required.png`
- `05-review-pack-available.png`
- `06-customer-safe-output-state.png`
- `07-export-unavailable.png`
- `08-diagnostics-collapsed.png`
- `09-dark-mode.png`
If a state is not reachable, document why; do not fake screenshots.
## Acceptance Criteria
### Product
- [ ] Evidence / Review Pack uses Product Process Flow.
- [ ] Decision card is visible.
- [ ] Evidence readiness state is clear.
- [ ] Review pack state is clear.
- [ ] Customer-safe output state is truthful.
- [ ] Export/download state is truthful.
- [ ] OperationRun proof is visible where available.
- [ ] Diagnostics collapsed.
- [ ] Raw payload hidden by default.
### Truth / Safety
- [ ] No fake evidence claim.
- [ ] No fake customer-safe claim.
- [ ] No fake auditor-ready claim.
- [ ] No fake export-ready claim.
- [ ] No fake coverage counts.
- [ ] OperationRun proof is separated from evidence output.
### Context / RBAC
- [ ] Workspace/environment/review isolation preserved.
- [ ] Capabilities respected.
- [ ] No `/admin/t`.
- [ ] No tenant query aliases.
- [ ] No cross-workspace evidence leakage.
### Validation
- [ ] Feature tests pass.
- [ ] Browser smoke passes.
- [ ] Screenshots captured or unreached states documented.
- [ ] Pint passes.
- [ ] `git diff --check` passes.
## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)*
| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction |
|---|---|---|---|---|---|---|---|
| Evidence Overview | Primary Decision Surface | Operator decides whether evidence/review pack work is ready, missing, stale, generating, failed, or exportable | Status, Reason, Impact, Primary next action, Evidence readiness flow, proof summary, customer-safe/export state | raw report metadata, raw evidence payload, operation diagnostics, artifact lists | Primary because it is the evidence proof workbench | Source data -> evidence -> report -> pack -> customer-safe -> export | Removes cross-page inference from files, IDs, and runs |
| Customer Review Workspace evidence path | Primary customer-safe consumption surface | Operator/customer reviewer decides whether a published review package can be shared or downloaded | Review readiness, evidence path, review pack state, customer-safe state, export availability | diagnostics, operation proof, internal artifact metadata | Primary for customer-safe consumption; only changed if needed | Review -> customer-safe package -> download | Prevents internal evidence from being mistaken for shareable output |
| Review Pack Resource detail | Secondary Evidence / Diagnostics | Operator inspects a generated pack and export proof | pack status, download availability, evidence snapshot link, operation proof | file metadata, summary/options diagnostics | Secondary because it is artifact detail, not the whole readiness chain | Artifact proof follows workbench decision | Keeps artifact detail from becoming the main readiness UX |
## Audience-Aware Disclosure *(mandatory when operator-facing surfaces are changed)*
| Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention |
|---|---|---|---|---|---|---|---|
| Evidence Overview | operator-MSP, manager, support reviewer | decision card, readiness flow, proof panel, customer-safe/export state | generation diagnostics, report metadata, operation failure summary | raw evidence payload, raw report payload, stack traces, raw OperationRun JSON | state-specific action: generate evidence, generate report, generate review pack, view operation, review customer output, or download export | raw/support sections collapsed and capability-aware | one top verdict; lower sections add proof only |
| Customer Review Workspace evidence path | customer-readable, operator-MSP, manager | review readiness, customer-safe state, package/download state, evidence path | internal proof and diagnostics | raw JSON, fingerprints, provider diagnostics, reason ownership | review customer output or download export when repo-backed | diagnostics collapsed; raw/internal detail hidden from customer-safe default path | do not duplicate top readiness with alternate wording |
| Review Pack Resource detail | operator-MSP, support reviewer | pack status, export/download proof, linked snapshot/review/run | options, summary, file metadata, operation proof | raw package metadata and generation diagnostics | download export or view operation | raw diagnostics collapsed/capability-aware | artifact status does not restate customer-safe readiness unless repo-backed |
## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)*
| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Evidence Overview | Workbench / Monitoring | Evidence and review-pack readiness workbench | Take the next generation/review/export action | explicit action links and artifact deep links | N/A | proof panel / aside | none introduced | `/admin/evidence/overview` | N/A | workspace + environment filter | Evidence / Review Pack | Status, Reason, Impact, flow, proof, customer-safe/export state | N/A |
| Customer Review Workspace evidence path | Workbench / Review | Customer-safe review consumption surface | review or download package | workspace page sections + artifact links | N/A | evidence path/proof sections | none introduced | `/admin/reviews/workspace` | N/A | workspace + environment filter | Customer Review | readiness, package availability, evidence path | N/A |
| Review Pack Resource | List / Detail | Artifact list/detail | inspect pack or download export | resource row/detail view | existing resource behavior | row/header actions | existing expire action remains grouped/confirmed | `/admin/workspaces/{workspace}/environments/{environment}/review-packs` | `/admin/workspaces/{workspace}/environments/{environment}/review-packs/{record}` | workspace + environment route | Review Pack | status, generated/export proof, linked evidence/review/run | N/A |
## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)*
| Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions |
|---|---|---|---|---|---|---|---|---|---|---|
| Evidence Overview | Workspace manager / governance operator | Decide whether evidence/review pack readiness requires generation, review, proof inspection, or export | Monitoring / decision-first workbench | Is this evidence package ready for customer or auditor consumption? | decision card, readiness flow, proof panel, customer-safe/export state | raw payload, generation diagnostics, failure details | data availability, evidence lifecycle, report availability, pack lifecycle, customer-safe readiness, export artifact availability, operation status/outcome | TenantPilot artifact generation/export only; no Microsoft tenant mutation introduced | generate/open evidence, open stored report, generate/open review pack, view operation, download export | none introduced |
| Customer Review Workspace evidence path | Customer reviewer / governance operator | Decide whether published review evidence can be shared/downloaded | Review / customer-safe consumption | Is this review package ready to share? | review readiness, evidence path, package/download state | internal diagnostics/proof | review lifecycle, evidence availability, accepted-risk follow-up, package availability | TenantPilot review/export artifacts only | review customer output, download export, open proof | none introduced |
| Review Pack Resource | Governance operator / support reviewer | Inspect generated pack and export proof | Artifact detail | Is this specific pack generated and downloadable? | pack status, file/download proof, linked snapshot/review/operation | options, summary, diagnostics | pack status, expiration, file availability, operation outcome | TenantPilot review-pack artifacts only | download, regenerate if authorized, view operation | existing expire action; no change |
## Proportionality Review *(mandatory when structural complexity is introduced)*
- **New source of truth?**: no
- **New persisted entity/table/artifact?**: no
- **New abstraction?**: yes, only a small presenter/view-model if runtime implementation needs one to avoid scattered Blade/Page state mapping.
- **New enum/state/reason family?**: no. Presentation states are derived from existing model statuses and the feature-local state contract.
- **New cross-domain UI framework/taxonomy?**: no. Reuse Spec 332 Product Process Flow.
- **Current operator problem**: evidence, report, review-pack, customer-safe, and export readiness are repo-real but not assembled into one decision-first flow.
- **Existing structure is insufficient because**: current surfaces expose proof and artifacts, but the operator still reconstructs readiness across pages, tables, file state, and operations.
- **Narrowest correct implementation**: derive a single flow model from existing artifacts and render it on the existing surfaces; keep backend generation, storage, OperationRun lifecycle, and route structure unchanged.
- **Ownership cost**: one focused presenter if needed, one feature state contract, feature/browser tests, and screenshots.
- **Alternative intentionally rejected**: a new persisted readiness engine, new artifact lifecycle table, new review-pack workflow service, or a generic document-management layer.
- **Release truth**: current-release UX alignment over existing enterprise evidence foundations.
### Compatibility posture
This feature assumes a pre-production environment.
Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by this spec.
Canonical replacement is preferred over preservation.
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
- **Test purpose / classification**: Feature + Browser.
- **Validation lane(s)**: confidence + browser.
- **Why this classification and these lanes are sufficient**: the risk is state composition, RBAC/context leakage, truthfulness, and visible product flow rendering, not isolated pure functions or backend engine behavior.
- **New or expanded test families**: `Spec337EvidenceReviewPackProductFlowTest` and `Spec337EvidenceReviewPackProductFlowSmokeTest`.
- **Fixture / helper cost impact**: reuse existing Evidence, ReviewPack, StoredReport, EnvironmentReview, OperationRun, workspace, and managed environment factories/helpers; no new global seeds or heavy defaults.
- **Heavy-family visibility / justification**: one explicit browser smoke file because this changes a strategic/customer-facing evidence path and screenshots are required.
- **Special surface test profile**: monitoring-state-page + shared-detail-family + customer-safe consumption path.
- **Standard-native relief or required special coverage**: dedicated coverage required because this is not a simple native resource list change.
- **Reviewer handoff**: verify lane fit, no hidden fixture growth, no raw diagnostics by default, no false customer-safe/export/auditor-ready copy, and capability-scoped actions.
- **Budget / baseline / trend impact**: none expected beyond one explicit browser smoke.
- **Escalation needed**: document-in-feature if a state is unreachable; follow-up-spec if implementation requires a new persisted readiness engine.
- **Active feature PR close-out entry**: Smoke Coverage.
- **Planned validation commands**:
```bash
cd apps/platform
./vendor/bin/sail artisan test tests/Feature/Filament/Spec337EvidenceReviewPackProductFlowTest.php --compact
./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec337EvidenceReviewPackProductFlowSmokeTest.php --compact
./vendor/bin/sail artisan test --filter='Evidence|ReviewPack|StoredReport|CustomerReview|ProductProcessFlow' --compact
./vendor/bin/sail pint --dirty
git diff --check
```
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Understand readiness from the first screen (Priority: P1)
As a governance operator, I need Evidence / Review Pack surfaces to tell me whether a package is ready for customer or auditor consumption without inspecting raw artifacts.
**Why this priority**: This is the core trust and workflow gap.
**Independent Test**: Render missing-evidence and ready-package fixtures and assert the decision card, readiness flow, primary next action, proof panel, and diagnostics behavior.
**Acceptance Scenarios**:
1. **Given** no evidence snapshot exists for the selected scope, **When** the Evidence / Review Pack surface renders, **Then** it shows `Evidence snapshot required`, marks the evidence step missing, marks review pack unavailable, marks customer-safe output not ready, and keeps diagnostics collapsed.
2. **Given** a ready package with a download-backed review pack exists, **When** the surface renders for an authorized user, **Then** it shows the pack/export state truthfully and does not claim auditor-ready unless the repo-backed customer-safe conditions are satisfied.
---
### User Story 2 - Follow the correct next action (Priority: P2)
As an operator, I need one primary action per state so I know whether to generate evidence, generate a report, generate a review pack, review customer output, view operation proof, or download export.
**Why this priority**: Evidence workflows are multi-step; competing CTAs create unsafe exports or repeated generation attempts.
**Independent Test**: Assert state-specific actions appear only for authorized users and that unauthorized users do not see generate/export actions.
**Acceptance Scenarios**:
1. **Given** an evidence snapshot exists but no report exists, **When** the page renders, **Then** the primary action is `Generate stored report` only if repo-supported and authorized.
2. **Given** a stored report exists but no review pack exists, **When** the page renders, **Then** the primary action is `Generate review pack` only for an authorized user.
---
### User Story 3 - Inspect proof without exposing raw internals (Priority: P3)
As a reviewer, I need OperationRun proof and artifact links while raw diagnostics remain secondary and collapsed.
**Why this priority**: Auditability requires proof, but customer-safe surfaces must not default to raw provider or payload detail.
**Independent Test**: Render linked OperationRun fixtures, failed runs, and cross-workspace runs and assert proof visibility, failed-proof copy, cross-workspace exclusion, and raw JSON hidden by default.
**Acceptance Scenarios**:
1. **Given** a linked OperationRun exists, **When** the proof panel renders, **Then** it shows status/outcome/timeline/requester/run type and an operation detail link when authorized.
2. **Given** a raw payload exists, **When** the page loads, **Then** raw JSON is not visible until the diagnostics disclosure is opened and only where capability conventions allow.
## Functional Requirements *(mandatory)*
- **FR-001**: Evidence / Review Pack surfaces MUST render the question `Is this evidence package ready for customer or auditor consumption?`.
- **FR-002**: The first screen MUST show Status, Reason, Impact, and Primary next action.
- **FR-003**: The Evidence readiness flow MUST use the Product Process Flow pattern and the six default steps.
- **FR-004**: The flow MUST derive step states from repo-backed artifacts only.
- **FR-005**: Customer-safe output MUST NOT be shown as ready unless the selected surface has repo-backed customer-safe/package/download readiness.
- **FR-006**: Export/download state MUST derive from ready, non-expired review packs with stored file metadata and authorized signed download access.
- **FR-007**: OperationRun proof MUST be shown where linked and authorized, and cross-workspace proof MUST NOT leak.
- **FR-008**: Diagnostics MUST be collapsed by default and raw payload/JSON MUST NOT be visible on initial render.
- **FR-009**: Generate/export/download/open actions MUST be capability-aware and preserve workspace/environment/review context.
- **FR-010**: The implementation MUST NOT introduce migrations, packages, env vars, queue/scheduler changes, new backend engines, or new persisted lifecycle truth.
## Non-Functional Requirements *(mandatory)*
- **NFR-001**: Page rendering remains database-only and must not call Microsoft Graph or external providers during render.
- **NFR-002**: Existing Filament v5 / Livewire v4 conventions remain intact.
- **NFR-003**: Panel provider registration remains in `apps/platform/bootstrap/providers.php`; no panel provider change is planned.
- **NFR-004**: Globally searchable resources touched by this spec must either have Edit/View pages or keep global search disabled. EvidenceSnapshotResource, ReviewPackResource, StoredReportResource, and EnvironmentReviewResource currently disable global search.
- **NFR-005**: Destructive actions remain confirmed and authorized. This spec introduces no new destructive actions.
- **NFR-006**: No global heavy assets are introduced; no deployment `filament:assets` change is expected.
## Key Dependencies
- Spec 332 - Product Process Flow System v1
- Spec 333 - Restore Create UX Final Productization
- Spec 335 - Restore Run Detail / Post-Execution Proof Productization
- Spec 336 - Baseline Compare Product Process Flow Alignment
- Spec 326 - Customer Review Workspace v1 Productization
- Spec 329 - Evidence / Audit Log Disclosure Productization
- Spec 325 - Screenshot-Anchored Strategic Target Images
## Follow-Up Specs *(out of scope)*
- Provider Readiness Productization
- Restore Evidence Export / Auditor Artifact Productization
- Cross-Tenant Compare and Promotion v1
- Customer Review Workspace Decision/Attestation Polish
## Implementation Close-Out Report
When implementation completes, report:
- Changed behavior.
- Evidence / Review Pack states covered.
- Product Process Flow reuse details.
- Files changed.
- Tests and results.
- Browser screenshots.
- Known gaps and unreachable states.
- Merge readiness.
- Confirmation that no migrations, packages, env vars, queues, scheduler, storage, deployment asset changes, destructive action behavior changes, or false customer-safe/evidence/export claims were introduced.

View File

@ -0,0 +1,259 @@
# Tasks: Spec 337 - Evidence Path / Review Pack Product Process Flow Alignment
- Input: `specs/337-evidence-review-pack-product-process-flow-alignment/spec.md`, `specs/337-evidence-review-pack-product-process-flow-alignment/plan.md`
- Prerequisites: `repo-truth-map.md`, `evidence-review-pack-state-contract.md`
- Preparation status: runtime implementation completed; checkboxes below reflect implementation and validation evidence.
**Tests**: Required. This changes strategic evidence/review surfaces and customer-safe package readiness presentation.
## Test Governance Checklist
- [x] Lane assignment remains explicit and narrowest sufficient (Feature + Browser).
- [x] Browser coverage stays single-file and scenario-scoped.
- [x] No new default-heavy helpers/factories/seeds are introduced; reuse existing fixture helpers.
- [x] Validation commands remain minimal and directly prove the changed contract.
- [x] Any unreachable state resolves as `document-in-feature` instead of fake screenshots or fake data.
## Phase 1: Preparation And Repo Truth
**Purpose**: Confirm repo truth and lock the state contract before runtime edits.
- [x] T001 Re-read `spec.md`, `plan.md`, this `tasks.md`, `repo-truth-map.md`, and `evidence-review-pack-state-contract.md`.
- [x] T002 Confirm working tree intent and record baseline commit (`git status`, `git log -1`).
- [x] T003 Re-verify related specs and guardrails:
- `specs/332-product-process-flow-system-v1/`
- `specs/326-customer-review-workspace-v1-productization/`
- `specs/329-evidence-audit-log-disclosure-productization/`
- `specs/336-baseline-compare-product-process-flow-alignment/`
- `.specify/memory/constitution.md`
- `docs/ai-coding-rules.md`
- `docs/filament-guidelines.md`
- `docs/security-guidelines.md`
- `docs/testing-guidelines.md`
- [x] T004 Re-verify repo truth sources and step semantics:
- `apps/platform/app/Models/EvidenceSnapshot.php`
- `apps/platform/app/Models/StoredReport.php`
- `apps/platform/app/Models/ReviewPack.php`
- `apps/platform/app/Models/EnvironmentReview.php`
- `apps/platform/app/Models/OperationRun.php`
- `apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php`
- `apps/platform/resources/views/filament/pages/monitoring/evidence-overview.blade.php`
- `apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php`
- `apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php`
- `apps/platform/app/Filament/Resources/ReviewPackResource.php`
- `apps/platform/app/Filament/Resources/StoredReportResource.php`
- `apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php`
- `apps/platform/app/Filament/Resources/EnvironmentReviewResource.php`
- `apps/platform/app/Services/Evidence/EvidenceSnapshotService.php`
- `apps/platform/app/Services/ReviewPackService.php`
- `apps/platform/app/Http/Controllers/ReviewPackDownloadController.php`
- [x] T005 Update `repo-truth-map.md` and `evidence-review-pack-state-contract.md` if implementation-time code differs from the prepared truth. No update required; implementation stayed within the prepared derived-state contract.
- [x] T006 Confirm Product Process Flow rendering conventions from Spec 332 and decide reuse strategy before editing UI.
## Phase 2: Presenter / Flow Model
**Purpose**: Centralize "what exists, what is missing, what is customer-safe, and what can be exported" without adding persisted truth.
- [x] T007 Decide whether a small `EvidenceReviewPackPresenter` is needed or whether existing page payload builders can produce the flow model cleanly.
- [x] T008 Implement the narrowest derived-only mapping for:
- decision card (`Status`, `Reason`, `Impact`, `Primary next action`)
- six readiness flow steps
- proof items
- coverage/contents summary
- customer-safe state
- export/download state
- diagnostics default state
- [x] T009 Ensure mapping uses existing models/statuses only and introduces no new enum/status/reason family.
- [x] T010 Ensure primary next action is exactly one per state and capability-aware.
- [x] T011 Ensure unsupported states render as unavailable/deferred with honest copy.
## Phase 3: Evidence Overview UI Alignment
**Purpose**: Make Evidence Overview the decision-first evidence readiness workbench.
- [x] T012 Add the decision question: `Is this evidence package ready for customer or auditor consumption?`
- [x] T013 Render `Status`, `Reason`, `Impact`, and `Primary next action` before raw artifact lists.
- [x] T014 Render `Evidence readiness flow` with Product Process Flow steps:
- Source data selected
- Evidence snapshot
- Stored report
- Review pack
- Customer-safe output
- Export / delivery
- [x] T015 Productize the Evidence Proof panel with rows for source data, snapshot, stored report, review pack, operation proof, export artifact, customer-safe state, and diagnostics.
- [x] T016 Keep raw artifact inventory secondary and diagnostics collapsed by default.
- [x] T017 Remove or avoid duplicated readiness/verdict blocks below the decision card.
- [x] T018 Ensure badges/status labels remain readable in light and dark mode.
## Phase 4: Review Pack / Customer Review Workspace / Export States
**Purpose**: Productize only repo-backed customer-safe and export states.
- [x] T019 Align Review Pack Resource list/detail copy or proof placement only where needed for state truth. No runtime change required; existing resource state/download semantics already matched the repo-truth contract.
- [x] T020 Align Customer Review Workspace evidence path only if current copy conflicts with the Spec 337 state contract. No runtime change required; existing customer-safe workspace tests remain the source of customer-safe readiness truth.
- [x] T021 Derive review-pack available/generating/failed/expired states from `ReviewPack.status`, `expires_at`, and file metadata.
- [x] T022 Derive export/download available only from ready, non-expired packs with `file_disk`, `file_path`, and authorized signed download.
- [x] T023 Render external delivery as unavailable unless a repo-backed delivery mechanism exists.
- [x] T024 Derive customer-safe output ready only from Customer Review Workspace / Environment Review readiness that is already repo-backed.
- [x] T025 Show coverage/contents metrics only if they exist in review/evidence/report summary data.
## Phase 5: OperationRun Proof / RBAC / Context / Diagnostics
**Purpose**: Preserve auditability and tenancy safety while hiding raw internals by default.
- [x] T026 Show OperationRun proof when linked and authorized:
- status
- started/completed timestamps
- requested by / initiator
- run type
- result/outcome
- operation detail link
- [x] T027 Show failed linked OperationRuns as failed proof, not as usable evidence output.
- [x] T028 Prevent cross-workspace/environment OperationRun and artifact links.
- [x] T029 Preserve workspace/environment/review query context in all secondary links.
- [x] T030 Keep diagnostics collapsed by default and hide raw JSON, raw payloads, stack traces, and internal exceptions on first render.
- [x] T031 Respect existing capabilities for generate evidence, generate report, generate review pack, export/download, open operation proof, open diagnostics, and open Customer Review Workspace.
- [x] T032 Do not add destructive actions; preserve confirmation and authorization on existing destructive/high-impact actions.
## Phase 6: Feature Tests (Pest)
- [x] T033 Add `apps/platform/tests/Feature/Filament/Spec337EvidenceReviewPackProductFlowTest.php`.
- [x] T034 Test missing evidence:
- decision question renders
- `Evidence snapshot required`
- flow visible
- evidence snapshot marked missing
- review pack unavailable
- customer-safe output not ready
- diagnostics collapsed
- no raw JSON visible
- [x] T035 Test evidence snapshot available / report missing when fixture-supported:
- evidence snapshot available
- stored report required
- no fake review pack ready claim
- [x] T036 Test review pack required when fixture-supported:
- stored report available
- review pack required
- generate review pack primary action only if authorized
- [x] T037 Test review pack available when fixture-supported:
- review pack available
- customer-safe state truthful
- export state truthful
- no false auditor-ready claim
- [x] T038 Test OperationRun proof:
- generation OperationRun visible when linked
- no cross-workspace OperationRun leak
- failed OperationRun shown as failed proof
- [x] T039 Test RBAC/context:
- unauthorized user cannot generate/export
- cross-workspace evidence not visible
- no legacy tenant alias
- [x] T040 Update existing Evidence/ReviewPack/CustomerReview tests only where assertions are strengthened.
## Phase 7: Browser Smoke + Screenshots
- [x] T041 Add `apps/platform/tests/Browser/Spec337EvidenceReviewPackProductFlowSmokeTest.php`.
- [x] T042 Cover browser states:
- missing evidence snapshot
- evidence generating if fixture-supported
- stored report available / review pack missing
- review pack available if fixture-supported
- export unavailable
- diagnostics collapsed
- dark mode if practical
- [x] T043 Assert in browser:
- Evidence readiness flow visible
- decision card visible
- proof panel visible
- customer-safe state visible
- raw payload hidden
- primary next action visible
- badges readable
- [x] T044 Capture screenshots into `specs/337-evidence-review-pack-product-process-flow-alignment/artifacts/screenshots/`:
- `01-evidence-snapshot-required.png`
- `02-evidence-generating.png`
- `03-stored-report-required.png`
- `04-review-pack-required.png`
- `05-review-pack-available.png`
- `06-customer-safe-output-state.png`
- `07-export-unavailable.png`
- `08-diagnostics-collapsed.png`
- `09-dark-mode.png`
- [x] T045 If a state is unreachable, document the repo-truth reason in implementation close-out. All required screenshot states were reachable with repo-backed fixtures.
## Phase 8: Validation
- [x] T046 Run narrow Feature tests:
```bash
cd apps/platform
./vendor/bin/sail artisan test tests/Feature/Filament/Spec337EvidenceReviewPackProductFlowTest.php --compact
```
- [x] T047 Run browser smoke:
```bash
cd apps/platform
./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec337EvidenceReviewPackProductFlowSmokeTest.php --compact
```
- [x] T048 Run overlapping guard filters. Command ran; unrelated dashboard/restore/customer-review failures reproduced individually and are documented in close-out:
```bash
cd apps/platform
./vendor/bin/sail artisan test --filter='Evidence|ReviewPack|StoredReport|CustomerReview|ProductProcessFlow' --compact
```
- [x] T049 Run formatting and whitespace checks:
```bash
cd apps/platform
./vendor/bin/sail pint --dirty
git diff --check
```
- [x] T050 Report full-suite status honestly if not run.
## Final Report Template
When implementation completes, report:
```text
Spec 337 completed.
Changed behavior:
...
Evidence / Review Pack states:
- Evidence missing:
- Evidence generating:
- Stored report required:
- Review pack required:
- Review pack available:
- Customer-safe state:
- Export state:
Product Process Flow:
...
Files changed:
...
Tests:
- command:
- result:
Browser screenshots:
...
Known gaps:
...
Merge readiness:
...
No migrations were created.
No packages, env vars, queues, scheduler, storage, or deployment asset changes were made.
No destructive action behavior was changed.
No false customer-safe/evidence/export claims were introduced.
```