feat: align evidence review pack product process flow (Spec 337) #407
@ -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())
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
@ -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*</'
|
||||
);
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
|
||||
|
After Width: | Height: | Size: 333 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 320 KiB |
|
After Width: | Height: | Size: 339 KiB |
|
After Width: | Height: | Size: 339 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 344 KiB |
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
```
|
||||