feat: add evidence anchor runtime closure contract proofs
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m5s
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m5s
This commit is contained in:
parent
c5db3ea4d1
commit
c737fd65a0
@ -613,7 +613,7 @@ private function evidenceReviewPackReadinessFlow(
|
|||||||
?EnvironmentReview $review,
|
?EnvironmentReview $review,
|
||||||
?ManagedEnvironment $tenant,
|
?ManagedEnvironment $tenant,
|
||||||
): array {
|
): array {
|
||||||
$sourceState = $tenant instanceof ManagedEnvironment ? 'Available' : 'Unavailable';
|
$sourceState = $tenant instanceof ManagedEnvironment ? 'Ready' : 'Not configured';
|
||||||
$snapshotState = $this->snapshotFlowState($snapshot);
|
$snapshotState = $this->snapshotFlowState($snapshot);
|
||||||
$storedReportState = $this->storedReportFlowState($snapshot, $storedReport);
|
$storedReportState = $this->storedReportFlowState($snapshot, $storedReport);
|
||||||
$reviewPackState = $this->reviewPackFlowState($snapshot, $storedReport, $reviewPack);
|
$reviewPackState = $this->reviewPackFlowState($snapshot, $storedReport, $reviewPack);
|
||||||
@ -621,7 +621,7 @@ private function evidenceReviewPackReadinessFlow(
|
|||||||
$exportState = $this->exportFlowState($snapshot, $reviewPack, $tenant);
|
$exportState = $this->exportFlowState($snapshot, $reviewPack, $tenant);
|
||||||
|
|
||||||
return [
|
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('Source data selected', $sourceState, $sourceState === 'Ready' ? '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('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('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('Review pack', $reviewPackState, $this->reviewPackFlowDescription($reviewPackState), $this->flowTone($reviewPackState), in_array($state, ['pack_required', 'pack_generating', 'pack_failed'], true)),
|
||||||
@ -697,14 +697,14 @@ private function evidenceReviewPackProofItems(
|
|||||||
: 'Operation proof unavailable. No generation operation is linked to this artifact.';
|
: 'Operation proof unavailable. No generation operation is linked to this artifact.';
|
||||||
|
|
||||||
return [
|
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->proofItem('Source data', $tenant instanceof ManagedEnvironment ? 'Ready' : 'Not configured', $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->snapshotProofCard($snapshot)),
|
||||||
$this->proofItemFromCard($this->storedReportProofCard($storedReport, $tenant)),
|
$this->proofItemFromCard($this->storedReportProofCard($storedReport, $tenant)),
|
||||||
$this->proofItemFromCard($this->reviewPackProofCard($reviewPack, $snapshot)),
|
$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('Operation proof', $operationRun instanceof OperationRun ? $this->operationProofState($operationRun) : 'Not configured', $operationDescription, $operationRun instanceof OperationRun ? $this->operationProofTone($operationRun) : 'gray'),
|
||||||
$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('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('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'),
|
$this->proofItem('Diagnostics', 'Not configured', 'Raw report metadata, raw evidence payloads, generation diagnostics, export diagnostics, provider diagnostics, stack traces, and internal exceptions stay collapsed by default.', 'gray'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -725,60 +725,68 @@ private function flowStep(string $label, string $state, string $description, str
|
|||||||
private function snapshotFlowState(?EvidenceSnapshot $snapshot): string
|
private function snapshotFlowState(?EvidenceSnapshot $snapshot): string
|
||||||
{
|
{
|
||||||
if (! $snapshot instanceof EvidenceSnapshot) {
|
if (! $snapshot instanceof EvidenceSnapshot) {
|
||||||
return 'Missing';
|
return 'Not configured';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->snapshotIsExpired($snapshot)) {
|
||||||
|
return 'Expired';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->snapshotHasStaleDimensions($snapshot)) {
|
||||||
|
return 'Needs attention';
|
||||||
}
|
}
|
||||||
|
|
||||||
return match ((string) $snapshot->status) {
|
return match ((string) $snapshot->status) {
|
||||||
EvidenceSnapshotStatus::Queued->value, EvidenceSnapshotStatus::Generating->value => 'Generating',
|
EvidenceSnapshotStatus::Queued->value, EvidenceSnapshotStatus::Generating->value => 'Running',
|
||||||
EvidenceSnapshotStatus::Failed->value => 'Failed',
|
EvidenceSnapshotStatus::Failed->value => 'Failed',
|
||||||
default => $this->snapshotIsStale($snapshot) ? 'Stale' : 'Available',
|
default => 'Ready',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private function storedReportFlowState(?EvidenceSnapshot $snapshot, ?StoredReport $storedReport): string
|
private function storedReportFlowState(?EvidenceSnapshot $snapshot, ?StoredReport $storedReport): string
|
||||||
{
|
{
|
||||||
if (! $snapshot instanceof EvidenceSnapshot || in_array($this->snapshotFlowState($snapshot), ['Generating', 'Failed'], true)) {
|
if (! $snapshot instanceof EvidenceSnapshot || in_array($this->snapshotFlowState($snapshot), ['Running', 'Failed', 'Expired'], true)) {
|
||||||
return 'Unavailable';
|
return 'Blocked';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $storedReport instanceof StoredReport ? 'Available' : 'Missing';
|
return $storedReport instanceof StoredReport ? 'Ready' : 'Not configured';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function reviewPackFlowState(?EvidenceSnapshot $snapshot, ?StoredReport $storedReport, ?ReviewPack $reviewPack): string
|
private function reviewPackFlowState(?EvidenceSnapshot $snapshot, ?StoredReport $storedReport, ?ReviewPack $reviewPack): string
|
||||||
{
|
{
|
||||||
if ($reviewPack instanceof ReviewPack) {
|
if ($reviewPack instanceof ReviewPack) {
|
||||||
return match ((string) $reviewPack->status) {
|
return match ((string) $reviewPack->status) {
|
||||||
ReviewPackStatus::Queued->value, ReviewPackStatus::Generating->value => 'Generating',
|
ReviewPackStatus::Queued->value, ReviewPackStatus::Generating->value => 'Running',
|
||||||
ReviewPackStatus::Failed->value => 'Failed',
|
ReviewPackStatus::Failed->value => 'Failed',
|
||||||
ReviewPackStatus::Ready->value => 'Available',
|
ReviewPackStatus::Ready->value => 'Ready',
|
||||||
default => 'Unavailable',
|
default => 'Blocked',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $snapshot instanceof EvidenceSnapshot) {
|
if (! $snapshot instanceof EvidenceSnapshot || ! $storedReport instanceof StoredReport) {
|
||||||
return 'Unavailable';
|
return 'Blocked';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $storedReport instanceof StoredReport ? 'Required' : 'Unavailable';
|
return 'Not configured';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function customerSafeFlowState(?EvidenceSnapshot $snapshot, ?ReviewPack $reviewPack, ?EnvironmentReview $review): string
|
private function customerSafeFlowState(?EvidenceSnapshot $snapshot, ?ReviewPack $reviewPack, ?EnvironmentReview $review): string
|
||||||
{
|
{
|
||||||
if (! $snapshot instanceof EvidenceSnapshot) {
|
if (! $snapshot instanceof EvidenceSnapshot) {
|
||||||
return 'Not ready';
|
return 'Not configured';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $reviewPack instanceof ReviewPack || ! $reviewPack->isReady()) {
|
if (! $reviewPack instanceof ReviewPack || ! $reviewPack->isReady()) {
|
||||||
return 'Not ready';
|
return 'Not configured';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->customerSafeOutputReady($review, $reviewPack) ? 'Ready' : 'Needs review';
|
return $this->customerSafeOutputReady($review, $reviewPack) ? 'Ready' : 'Needs attention';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function exportFlowState(?EvidenceSnapshot $snapshot, ?ReviewPack $reviewPack, ?ManagedEnvironment $tenant): string
|
private function exportFlowState(?EvidenceSnapshot $snapshot, ?ReviewPack $reviewPack, ?ManagedEnvironment $tenant): string
|
||||||
{
|
{
|
||||||
if (! $snapshot instanceof EvidenceSnapshot || ! $reviewPack instanceof ReviewPack) {
|
if (! $snapshot instanceof EvidenceSnapshot || ! $reviewPack instanceof ReviewPack) {
|
||||||
return 'Unavailable';
|
return 'Not configured';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((string) $reviewPack->status === ReviewPackStatus::Failed->value) {
|
if ((string) $reviewPack->status === ReviewPackStatus::Failed->value) {
|
||||||
@ -786,22 +794,23 @@ private function exportFlowState(?EvidenceSnapshot $snapshot, ?ReviewPack $revie
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (! $reviewPack->isReady()) {
|
if (! $reviewPack->isReady()) {
|
||||||
return 'Unavailable';
|
return 'Blocked';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->reviewPackHasExportArtifact($reviewPack) && $this->canDownloadReviewPack($reviewPack, $tenant)
|
return $this->reviewPackHasExportArtifact($reviewPack) && $this->canDownloadReviewPack($reviewPack, $tenant)
|
||||||
? 'Available'
|
? 'Ready'
|
||||||
: 'Required';
|
: 'Needs attention';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function snapshotFlowDescription(string $state): string
|
private function snapshotFlowDescription(string $state): string
|
||||||
{
|
{
|
||||||
return match ($state) {
|
return match ($state) {
|
||||||
'Available' => 'Snapshot proof exists.',
|
'Ready' => 'Snapshot proof exists.',
|
||||||
'Missing' => 'No snapshot in scope.',
|
'Not configured' => 'No snapshot in scope.',
|
||||||
'Generating' => 'Generation is running.',
|
'Running' => 'Generation is running.',
|
||||||
'Failed' => 'Generation failed.',
|
'Failed' => 'Generation failed.',
|
||||||
'Stale' => 'Evidence is stale or expired.',
|
'Expired' => 'Evidence has expired.',
|
||||||
|
'Needs attention' => 'Evidence has stale dimensions.',
|
||||||
default => 'Evidence snapshot state is unavailable.',
|
default => 'Evidence snapshot state is unavailable.',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -809,8 +818,8 @@ private function snapshotFlowDescription(string $state): string
|
|||||||
private function storedReportFlowDescription(string $state): string
|
private function storedReportFlowDescription(string $state): string
|
||||||
{
|
{
|
||||||
return match ($state) {
|
return match ($state) {
|
||||||
'Available' => 'Stored report exists.',
|
'Ready' => 'Stored report exists.',
|
||||||
'Missing' => 'No report for this output.',
|
'Not configured' => 'No report for this output.',
|
||||||
default => 'Depends on snapshot availability.',
|
default => 'Depends on snapshot availability.',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -818,9 +827,9 @@ private function storedReportFlowDescription(string $state): string
|
|||||||
private function reviewPackFlowDescription(string $state): string
|
private function reviewPackFlowDescription(string $state): string
|
||||||
{
|
{
|
||||||
return match ($state) {
|
return match ($state) {
|
||||||
'Available' => 'Review pack exists.',
|
'Ready' => 'Review pack exists.',
|
||||||
'Required' => 'Generate a review pack.',
|
'Not configured' => 'Generate a review pack.',
|
||||||
'Generating' => 'Generation is running.',
|
'Running' => 'Generation is running.',
|
||||||
'Failed' => 'Generation failed.',
|
'Failed' => 'Generation failed.',
|
||||||
default => 'Blocked by earlier proof.',
|
default => 'Blocked by earlier proof.',
|
||||||
};
|
};
|
||||||
@ -830,8 +839,8 @@ private function customerSafeFlowDescription(string $state): string
|
|||||||
{
|
{
|
||||||
return match ($state) {
|
return match ($state) {
|
||||||
'Ready' => 'Published review backs current export pack.',
|
'Ready' => 'Published review backs current export pack.',
|
||||||
'Needs review' => 'Readiness is not confirmed.',
|
'Needs attention' => 'Readiness is not confirmed.',
|
||||||
'Not ready' => 'Customer-safe output is not ready.',
|
'Not configured' => 'Customer-safe output is not ready.',
|
||||||
default => 'Customer-safe output readiness is unavailable.',
|
default => 'Customer-safe output readiness is unavailable.',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -839,8 +848,8 @@ private function customerSafeFlowDescription(string $state): string
|
|||||||
private function exportFlowDescription(string $state): string
|
private function exportFlowDescription(string $state): string
|
||||||
{
|
{
|
||||||
return match ($state) {
|
return match ($state) {
|
||||||
'Available' => 'Authorized download is available.',
|
'Ready' => 'Authorized download is available.',
|
||||||
'Required' => 'Export file is missing or unauthorized.',
|
'Needs attention' => 'Export file is missing or unauthorized.',
|
||||||
'Failed' => 'The linked review-pack export failed.',
|
'Failed' => 'The linked review-pack export failed.',
|
||||||
default => 'No generated export is available.',
|
default => 'No generated export is available.',
|
||||||
};
|
};
|
||||||
@ -849,9 +858,10 @@ private function exportFlowDescription(string $state): string
|
|||||||
private function flowTone(string $state): string
|
private function flowTone(string $state): string
|
||||||
{
|
{
|
||||||
return match ($state) {
|
return match ($state) {
|
||||||
'Available', 'Ready', 'Generated' => 'success',
|
'Ready' => 'success',
|
||||||
'Generating' => 'info',
|
'Historical', 'Running' => 'info',
|
||||||
'Missing', 'Required', 'Stale', 'Needs review', 'Not ready' => 'warning',
|
'Not configured', 'Needs attention', 'Expired' => 'warning',
|
||||||
|
'Blocked' => 'danger',
|
||||||
'Failed' => 'danger',
|
'Failed' => 'danger',
|
||||||
default => 'gray',
|
default => 'gray',
|
||||||
};
|
};
|
||||||
@ -859,18 +869,59 @@ private function flowTone(string $state): string
|
|||||||
|
|
||||||
private function snapshotIsStale(EvidenceSnapshot $snapshot): bool
|
private function snapshotIsStale(EvidenceSnapshot $snapshot): bool
|
||||||
{
|
{
|
||||||
if ((string) $snapshot->status === EvidenceSnapshotStatus::Expired->value) {
|
return $this->snapshotIsExpired($snapshot)
|
||||||
return true;
|
|| $this->snapshotHasStaleDimensions($snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($snapshot->expires_at instanceof \DateTimeInterface && $snapshot->expires_at->isPast()) {
|
private function snapshotIsExpired(EvidenceSnapshot $snapshot): bool
|
||||||
return true;
|
{
|
||||||
}
|
return (string) $snapshot->status === EvidenceSnapshotStatus::Expired->value
|
||||||
|
|| ($snapshot->expires_at instanceof \DateTimeInterface && $snapshot->expires_at->isPast());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function snapshotHasStaleDimensions(EvidenceSnapshot $snapshot): bool
|
||||||
|
{
|
||||||
return $snapshot->completenessState() === EvidenceCompletenessState::Stale
|
return $snapshot->completenessState() === EvidenceCompletenessState::Stale
|
||||||
|| (int) data_get($snapshot->summary ?? [], 'stale_dimensions', 0) > 0;
|
|| (int) data_get($snapshot->summary ?? [], 'stale_dimensions', 0) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function snapshotHasUsableCurrentContent(EvidenceSnapshot $snapshot, ?ArtifactTruthEnvelope $truth = null): bool
|
||||||
|
{
|
||||||
|
$truth ??= $this->snapshotTruth($snapshot);
|
||||||
|
|
||||||
|
return $truth->contentState === 'trusted'
|
||||||
|
&& $truth->freshnessState === 'current'
|
||||||
|
&& (int) data_get($snapshot->summary ?? [], 'dimension_count', 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function snapshotProofState(EvidenceSnapshot $snapshot): string
|
||||||
|
{
|
||||||
|
if ($this->snapshotIsExpired($snapshot)) {
|
||||||
|
return 'Expired';
|
||||||
|
}
|
||||||
|
|
||||||
|
return match ((string) $snapshot->status) {
|
||||||
|
EvidenceSnapshotStatus::Queued->value, EvidenceSnapshotStatus::Generating->value => 'Running',
|
||||||
|
EvidenceSnapshotStatus::Failed->value => 'Failed',
|
||||||
|
EvidenceSnapshotStatus::Superseded->value => 'Historical',
|
||||||
|
default => $this->snapshotHasUsableCurrentContent($snapshot) ? 'Ready' : 'Needs attention',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function evidenceInventoryOutcomeState(EvidenceSnapshot $snapshot, ArtifactTruthEnvelope $truth): string
|
||||||
|
{
|
||||||
|
if ($this->snapshotIsExpired($snapshot)) {
|
||||||
|
return 'Expired';
|
||||||
|
}
|
||||||
|
|
||||||
|
return match ((string) $snapshot->status) {
|
||||||
|
EvidenceSnapshotStatus::Queued->value, EvidenceSnapshotStatus::Generating->value => 'Running',
|
||||||
|
EvidenceSnapshotStatus::Failed->value => 'Failed',
|
||||||
|
EvidenceSnapshotStatus::Superseded->value => 'Historical',
|
||||||
|
default => $this->snapshotHasUsableCurrentContent($snapshot, $truth) ? 'Ready' : 'Needs attention',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private function reviewPackHasExportArtifact(?ReviewPack $reviewPack): bool
|
private function reviewPackHasExportArtifact(?ReviewPack $reviewPack): bool
|
||||||
{
|
{
|
||||||
if (! $reviewPack instanceof ReviewPack || ! $reviewPack->isReady()) {
|
if (! $reviewPack instanceof ReviewPack || ! $reviewPack->isReady()) {
|
||||||
@ -948,13 +999,18 @@ private function proofItemFromCard(array $card): array
|
|||||||
|
|
||||||
private function operationProofState(OperationRun $operationRun): string
|
private function operationProofState(OperationRun $operationRun): string
|
||||||
{
|
{
|
||||||
if ((string) $operationRun->outcome === OperationRunOutcome::Failed->value) {
|
if (in_array((string) $operationRun->status, [OperationRunStatus::Queued->value, OperationRunStatus::Running->value], true)) {
|
||||||
return 'Failed';
|
return 'Running';
|
||||||
}
|
}
|
||||||
|
|
||||||
return in_array((string) $operationRun->status, [OperationRunStatus::Queued->value, OperationRunStatus::Running->value], true)
|
return match ((string) $operationRun->outcome) {
|
||||||
? 'Generating'
|
OperationRunOutcome::Succeeded->value => 'Historical',
|
||||||
: 'Available';
|
OperationRunOutcome::PartiallySucceeded->value => 'Needs attention',
|
||||||
|
OperationRunOutcome::Blocked->value => 'Blocked',
|
||||||
|
OperationRunOutcome::Failed->value,
|
||||||
|
OperationRunOutcome::Cancelled->value => 'Failed',
|
||||||
|
default => 'Needs attention',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private function operationProofTone(OperationRun $operationRun): string
|
private function operationProofTone(OperationRun $operationRun): string
|
||||||
@ -994,7 +1050,7 @@ private function decisionEvidenceSummary(
|
|||||||
$parts[] = 'Export: '.$this->exportFlowState($snapshot, $reviewPack, $tenant);
|
$parts[] = 'Export: '.$this->exportFlowState($snapshot, $reviewPack, $tenant);
|
||||||
|
|
||||||
if ($operationRun instanceof OperationRun) {
|
if ($operationRun instanceof OperationRun) {
|
||||||
$parts[] = OperationRunLinks::identifier($operationRun);
|
$parts[] = 'Operation proof: '.$this->operationProofState($operationRun);
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(' · ', $parts);
|
return implode(' · ', $parts);
|
||||||
@ -1034,8 +1090,10 @@ private function decisionActionDescription(string $state, ?array $primaryAction)
|
|||||||
|
|
||||||
return match ($state) {
|
return match ($state) {
|
||||||
'no_snapshot' => 'Creates the evidence snapshot required before reports or review packs can be trusted.',
|
'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_generating' => 'Evidence generation is still running; operation history stays in technical monitoring.',
|
||||||
'snapshot_failed', 'pack_failed' => 'Opens failed operation proof before retrying or sharing any output.',
|
'snapshot_failed' => 'Evidence generation failed; operation history stays in technical monitoring.',
|
||||||
|
'pack_generating' => 'Opens the review-pack context while generation proof stays in technical monitoring.',
|
||||||
|
'pack_failed' => 'Opens the review-pack context before retrying or sharing any output.',
|
||||||
'snapshot_stale' => 'Opens the stale snapshot so evidence can be refreshed from the correct scope.',
|
'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.',
|
'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.',
|
'pack_required' => 'Opens the environment review-pack surface where generation stays capability-gated.',
|
||||||
@ -1180,12 +1238,10 @@ private function primaryEvidenceAction(
|
|||||||
?ManagedEnvironment $tenant,
|
?ManagedEnvironment $tenant,
|
||||||
?OperationRun $operationRun,
|
?OperationRun $operationRun,
|
||||||
): ?array {
|
): ?array {
|
||||||
if (in_array($state, ['snapshot_generating', 'snapshot_failed', 'pack_generating', 'pack_failed'], true) && $operationRun instanceof OperationRun) {
|
if (in_array($state, ['pack_generating', 'pack_failed'], true) && $reviewPack instanceof ReviewPack && $reviewPack->tenant instanceof ManagedEnvironment) {
|
||||||
return [
|
return [
|
||||||
'label' => $state === 'snapshot_generating' || $state === 'pack_generating'
|
'label' => 'Open review pack',
|
||||||
? 'View operation progress'
|
'url' => ReviewPackResource::getUrl('view', ['record' => $reviewPack], tenant: $reviewPack->tenant, panel: 'admin'),
|
||||||
: 'Review operation',
|
|
||||||
'url' => OperationRunLinks::tenantlessView($operationRun),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1271,13 +1327,6 @@ private function primaryEvidenceAction(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($operationRun instanceof OperationRun) {
|
|
||||||
return [
|
|
||||||
'label' => OperationRunLinks::openLabel(),
|
|
||||||
'url' => OperationRunLinks::tenantlessView($operationRun),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1286,21 +1335,9 @@ private function primaryEvidenceAction(
|
|||||||
*/
|
*/
|
||||||
private function currentEvidenceActionForSnapshot(EvidenceSnapshot $snapshot): ?array
|
private function currentEvidenceActionForSnapshot(EvidenceSnapshot $snapshot): ?array
|
||||||
{
|
{
|
||||||
$tenant = $snapshot->tenant;
|
$anchor = $this->currentAnchorForSnapshot($snapshot);
|
||||||
$workspace = $snapshot->workspace;
|
|
||||||
$user = auth()->user();
|
|
||||||
|
|
||||||
if (! $tenant instanceof ManagedEnvironment || ! $workspace instanceof Workspace) {
|
if (! $anchor instanceof EvidenceAnchorResult || ! $anchor->canLink || ! is_string($anchor->targetRoute)) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$anchor = $this->evidenceAnchors()->currentForScope(
|
|
||||||
$workspace,
|
|
||||||
$tenant,
|
|
||||||
$user instanceof User ? $user : null,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (! $anchor->canLink || ! is_string($anchor->targetRoute)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1332,7 +1369,7 @@ private function snapshotProofCard(?EvidenceSnapshot $snapshot): array
|
|||||||
if (! $snapshot instanceof EvidenceSnapshot) {
|
if (! $snapshot instanceof EvidenceSnapshot) {
|
||||||
return $this->unavailableProofCard(
|
return $this->unavailableProofCard(
|
||||||
'Current evidence',
|
'Current evidence',
|
||||||
'Not generated',
|
'Not configured',
|
||||||
'No complete active evidence snapshot is available in this scope.',
|
'No complete active evidence snapshot is available in this scope.',
|
||||||
'gray',
|
'gray',
|
||||||
);
|
);
|
||||||
@ -1341,15 +1378,16 @@ private function snapshotProofCard(?EvidenceSnapshot $snapshot): array
|
|||||||
$outcome = $this->snapshotOutcome($snapshot);
|
$outcome = $this->snapshotOutcome($snapshot);
|
||||||
$isEmptySnapshot = $this->isEmptyEvidenceSnapshot($snapshot);
|
$isEmptySnapshot = $this->isEmptyEvidenceSnapshot($snapshot);
|
||||||
$anchor = $this->currentAnchorForSnapshot($snapshot);
|
$anchor = $this->currentAnchorForSnapshot($snapshot);
|
||||||
|
$state = $this->snapshotProofState($snapshot);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'label' => 'Current evidence',
|
'label' => 'Current evidence',
|
||||||
'value' => $isEmptySnapshot ? 'Proof incomplete' : $outcome->primaryLabel,
|
'value' => $state,
|
||||||
'path_state' => $isEmptySnapshot ? 'Empty' : $outcome->primaryLabel,
|
'path_state' => $state,
|
||||||
'description' => $isEmptySnapshot
|
'description' => $isEmptySnapshot
|
||||||
? 'A proof record exists, but no usable captured evidence is available yet.'
|
? 'A proof record exists, but no usable captured evidence is available yet.'
|
||||||
: $this->productSafeEvidenceReason($outcome->primaryReason),
|
: $this->productSafeEvidenceReason($outcome->primaryReason),
|
||||||
'color' => $outcome->primaryBadge->color,
|
'color' => $this->flowTone($state),
|
||||||
'url' => $anchor instanceof EvidenceAnchorResult && $anchor->canLink ? $anchor->targetRoute : null,
|
'url' => $anchor instanceof EvidenceAnchorResult && $anchor->canLink ? $anchor->targetRoute : null,
|
||||||
'action_label' => $anchor instanceof EvidenceAnchorResult && $anchor->canLink
|
'action_label' => $anchor instanceof EvidenceAnchorResult && $anchor->canLink
|
||||||
? 'View internal evidence details'
|
? 'View internal evidence details'
|
||||||
@ -1368,11 +1406,15 @@ private function currentAnchorForSnapshot(EvidenceSnapshot $snapshot): ?Evidence
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->evidenceAnchors()->currentForScope(
|
$anchor = $this->evidenceAnchors()->currentForScope(
|
||||||
$workspace,
|
$workspace,
|
||||||
$tenant,
|
$tenant,
|
||||||
$user instanceof User ? $user : null,
|
$user instanceof User ? $user : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return (int) ($anchor->evidenceSnapshotId ?? 0) === (int) $snapshot->getKey()
|
||||||
|
? $anchor
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1383,7 +1425,7 @@ private function reviewPackProofCard(?ReviewPack $reviewPack, ?EvidenceSnapshot
|
|||||||
if (! $reviewPack instanceof ReviewPack) {
|
if (! $reviewPack instanceof ReviewPack) {
|
||||||
return $this->unavailableProofCard(
|
return $this->unavailableProofCard(
|
||||||
'Review pack',
|
'Review pack',
|
||||||
$snapshot instanceof EvidenceSnapshot ? 'Not generated' : 'Not applicable',
|
$snapshot instanceof EvidenceSnapshot ? 'Not configured' : 'Blocked',
|
||||||
$snapshot instanceof EvidenceSnapshot
|
$snapshot instanceof EvidenceSnapshot
|
||||||
? 'No review pack has been generated from the current evidence snapshot.'
|
? 'No review pack has been generated from the current evidence snapshot.'
|
||||||
: 'A review pack requires an evidence snapshot first.',
|
: 'A review pack requires an evidence snapshot first.',
|
||||||
@ -1393,11 +1435,11 @@ private function reviewPackProofCard(?ReviewPack $reviewPack, ?EvidenceSnapshot
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'label' => 'Review pack',
|
'label' => 'Review pack',
|
||||||
'value' => BadgeRenderer::label(BadgeDomain::ReviewPackStatus)((string) $reviewPack->status),
|
'value' => $this->reviewPackProofState($reviewPack),
|
||||||
'description' => $reviewPack->isReady()
|
'description' => $reviewPack->isReady()
|
||||||
? 'Customer-review artifact exists for this evidence path.'
|
? 'Customer-review artifact exists for this evidence path.'
|
||||||
: 'Review pack exists but is not ready for sharing.',
|
: 'Review pack exists but is not ready for sharing.',
|
||||||
'color' => BadgeRenderer::color(BadgeDomain::ReviewPackStatus)((string) $reviewPack->status),
|
'color' => $this->flowTone($this->reviewPackProofState($reviewPack)),
|
||||||
'url' => $reviewPack->tenant instanceof ManagedEnvironment
|
'url' => $reviewPack->tenant instanceof ManagedEnvironment
|
||||||
? ReviewPackResource::getUrl('view', ['record' => $reviewPack], tenant: $reviewPack->tenant, panel: 'admin')
|
? ReviewPackResource::getUrl('view', ['record' => $reviewPack], tenant: $reviewPack->tenant, panel: 'admin')
|
||||||
: null,
|
: null,
|
||||||
@ -1405,6 +1447,20 @@ private function reviewPackProofCard(?ReviewPack $reviewPack, ?EvidenceSnapshot
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function reviewPackProofState(ReviewPack $reviewPack): string
|
||||||
|
{
|
||||||
|
if ((string) $reviewPack->status === ReviewPackStatus::Expired->value || ($reviewPack->expires_at instanceof \DateTimeInterface && $reviewPack->expires_at->isPast())) {
|
||||||
|
return 'Expired';
|
||||||
|
}
|
||||||
|
|
||||||
|
return match ((string) $reviewPack->status) {
|
||||||
|
ReviewPackStatus::Queued->value, ReviewPackStatus::Generating->value => 'Running',
|
||||||
|
ReviewPackStatus::Ready->value => 'Ready',
|
||||||
|
ReviewPackStatus::Failed->value => 'Failed',
|
||||||
|
default => 'Blocked',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
@ -1413,7 +1469,7 @@ private function storedReportProofCard(?StoredReport $storedReport, ?ManagedEnvi
|
|||||||
if (! $tenant instanceof ManagedEnvironment) {
|
if (! $tenant instanceof ManagedEnvironment) {
|
||||||
return $this->unavailableProofCard(
|
return $this->unavailableProofCard(
|
||||||
'Stored report / export',
|
'Stored report / export',
|
||||||
'Not applicable',
|
'Not configured',
|
||||||
'Stored report availability is evaluated after an evidence scope exists.',
|
'Stored report availability is evaluated after an evidence scope exists.',
|
||||||
'gray',
|
'gray',
|
||||||
);
|
);
|
||||||
@ -1422,7 +1478,7 @@ private function storedReportProofCard(?StoredReport $storedReport, ?ManagedEnvi
|
|||||||
if (! $storedReport instanceof StoredReport) {
|
if (! $storedReport instanceof StoredReport) {
|
||||||
return $this->unavailableProofCard(
|
return $this->unavailableProofCard(
|
||||||
'Stored report / export',
|
'Stored report / export',
|
||||||
'Unavailable',
|
'Not configured',
|
||||||
'No repo-supported stored report is available for this environment scope.',
|
'No repo-supported stored report is available for this environment scope.',
|
||||||
'gray',
|
'gray',
|
||||||
);
|
);
|
||||||
@ -1430,7 +1486,7 @@ private function storedReportProofCard(?StoredReport $storedReport, ?ManagedEnvi
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'label' => 'Stored report / export',
|
'label' => 'Stored report / export',
|
||||||
'value' => 'Available',
|
'value' => 'Ready',
|
||||||
'description' => StoredReportResource::reportFamilyReportLabel((string) $storedReport->report_type),
|
'description' => StoredReportResource::reportFamilyReportLabel((string) $storedReport->report_type),
|
||||||
'color' => 'success',
|
'color' => 'success',
|
||||||
'url' => StoredReportResource::getUrl('view', ['record' => $storedReport], tenant: $tenant, panel: 'admin'),
|
'url' => StoredReportResource::getUrl('view', ['record' => $storedReport], tenant: $tenant, panel: 'admin'),
|
||||||
@ -1446,7 +1502,7 @@ private function operationProofCard(?OperationRun $operationRun): array
|
|||||||
if (! $operationRun instanceof OperationRun) {
|
if (! $operationRun instanceof OperationRun) {
|
||||||
return $this->unavailableProofCard(
|
return $this->unavailableProofCard(
|
||||||
'Operation proof',
|
'Operation proof',
|
||||||
'Unavailable',
|
'Not configured',
|
||||||
'No authorized operation run is linked to the current proof path.',
|
'No authorized operation run is linked to the current proof path.',
|
||||||
'gray',
|
'gray',
|
||||||
);
|
);
|
||||||
@ -1454,10 +1510,10 @@ private function operationProofCard(?OperationRun $operationRun): array
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'label' => 'Operation proof',
|
'label' => 'Operation proof',
|
||||||
'value' => 'Available',
|
'value' => $this->operationProofState($operationRun),
|
||||||
'description' => OperationRunLinks::identifier($operationRun),
|
'description' => 'Authorized operation history is linked to this proof path.',
|
||||||
'color' => 'info',
|
'color' => $this->operationProofTone($operationRun),
|
||||||
'url' => OperationRunLinks::tenantlessView($operationRun),
|
'url' => null,
|
||||||
'meta' => $operationRun->completed_at?->diffForHumans() ?? $operationRun->created_at?->diffForHumans() ?? 'Run time unavailable',
|
'meta' => $operationRun->completed_at?->diffForHumans() ?? $operationRun->created_at?->diffForHumans() ?? 'Run time unavailable',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -1538,7 +1594,7 @@ private function primaryProofState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'label' => 'Proof incomplete',
|
'label' => 'Needs attention',
|
||||||
'color' => 'warning',
|
'color' => 'warning',
|
||||||
'reason' => 'Primary evidence snapshot is empty.',
|
'reason' => 'Primary evidence snapshot is empty.',
|
||||||
'impact' => $this->emptySnapshotImpact($reviewPack, $storedReport, $operationRun),
|
'impact' => $this->emptySnapshotImpact($reviewPack, $storedReport, $operationRun),
|
||||||
@ -1748,6 +1804,7 @@ private function rowForSnapshot(EvidenceSnapshot $snapshot, array $currentReview
|
|||||||
$truth = $this->snapshotTruth($snapshot);
|
$truth = $this->snapshotTruth($snapshot);
|
||||||
$outcome = $this->snapshotOutcome($snapshot);
|
$outcome = $this->snapshotOutcome($snapshot);
|
||||||
$anchor = $this->currentAnchorForSnapshot($snapshot);
|
$anchor = $this->currentAnchorForSnapshot($snapshot);
|
||||||
|
$outcomeState = $this->evidenceInventoryOutcomeState($snapshot, $truth);
|
||||||
$tenantId = (int) $snapshot->managed_environment_id;
|
$tenantId = (int) $snapshot->managed_environment_id;
|
||||||
$hasCurrentReview = $currentReviewTenantIds[$tenantId] ?? false;
|
$hasCurrentReview = $currentReviewTenantIds[$tenantId] ?? false;
|
||||||
$nextStep = ! $hasCurrentReview && $truth->contentState === 'trusted' && $truth->freshnessState === 'current'
|
$nextStep = ! $hasCurrentReview && $truth->contentState === 'trusted' && $truth->freshnessState === 'current'
|
||||||
@ -1759,13 +1816,13 @@ private function rowForSnapshot(EvidenceSnapshot $snapshot, array $currentReview
|
|||||||
'managed_environment_id' => $tenantId,
|
'managed_environment_id' => $tenantId,
|
||||||
'snapshot_id' => (int) $snapshot->getKey(),
|
'snapshot_id' => (int) $snapshot->getKey(),
|
||||||
'generated_at' => $snapshot->generated_at?->toDateTimeString(),
|
'generated_at' => $snapshot->generated_at?->toDateTimeString(),
|
||||||
'artifact_truth_label' => $truth->contentState === 'empty' ? 'Proof incomplete' : $outcome->primaryLabel,
|
'artifact_truth_label' => $outcomeState,
|
||||||
'artifact_truth_color' => $outcome->primaryBadge->color,
|
'artifact_truth_color' => $this->flowTone($outcomeState),
|
||||||
'artifact_truth_icon' => $outcome->primaryBadge->icon,
|
'artifact_truth_icon' => $outcome->primaryBadge->icon,
|
||||||
'artifact_truth_explanation' => $this->productSafeEvidenceReason($outcome->primaryReason),
|
'artifact_truth_explanation' => $this->productSafeEvidenceReason($outcome->primaryReason),
|
||||||
'artifact_truth' => [
|
'artifact_truth' => [
|
||||||
'label' => $truth->contentState === 'empty' ? 'Proof incomplete' : $outcome->primaryLabel,
|
'label' => $outcomeState,
|
||||||
'color' => $outcome->primaryBadge->color,
|
'color' => $this->flowTone($outcomeState),
|
||||||
'icon' => $outcome->primaryBadge->icon,
|
'icon' => $outcome->primaryBadge->icon,
|
||||||
'explanation' => $this->productSafeEvidenceReason($outcome->primaryReason),
|
'explanation' => $this->productSafeEvidenceReason($outcome->primaryReason),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -492,7 +492,7 @@ public function latestReviewConsumptionPayload(): ?array
|
|||||||
'status_color' => $this->latestReviewStateColor($tenant),
|
'status_color' => $this->latestReviewStateColor($tenant),
|
||||||
'published_label' => $publishedAt instanceof \DateTimeInterface
|
'published_label' => $publishedAt instanceof \DateTimeInterface
|
||||||
? $publishedAt->format('M j, Y H:i')
|
? $publishedAt->format('M j, Y H:i')
|
||||||
: __('localization.review.unavailable'),
|
: $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
'package_label' => $packageAvailability['label'],
|
'package_label' => $packageAvailability['label'],
|
||||||
'package_badge_label' => $this->governancePackageAvailabilityLabel($tenant),
|
'package_badge_label' => $this->governancePackageAvailabilityLabel($tenant),
|
||||||
'package_color' => $this->governancePackageAvailabilityColor($tenant),
|
'package_color' => $this->governancePackageAvailabilityColor($tenant),
|
||||||
@ -776,20 +776,19 @@ private function reviewReadinessForTenant(
|
|||||||
)
|
)
|
||||||
: $resolutionCase;
|
: $resolutionCase;
|
||||||
$presentedResolutionCase = $this->decorateSuccessorResolutionCase($presentedResolutionCase, $review);
|
$presentedResolutionCase = $this->decorateSuccessorResolutionCase($presentedResolutionCase, $review);
|
||||||
|
$presentedResolutionCase = array_replace($presentedResolutionCase, [
|
||||||
|
'title' => $this->canonicalWorkspaceResolutionTitle($presentedResolutionCase, $effectiveState),
|
||||||
|
]);
|
||||||
$primaryAction = is_array($presentedResolutionCase['primary_action'] ?? null) ? $presentedResolutionCase['primary_action'] : null;
|
$primaryAction = is_array($presentedResolutionCase['primary_action'] ?? null) ? $presentedResolutionCase['primary_action'] : null;
|
||||||
$secondaryActions = is_array($presentedResolutionCase['secondary_actions'] ?? null) ? $presentedResolutionCase['secondary_actions'] : [];
|
$secondaryActions = is_array($presentedResolutionCase['secondary_actions'] ?? null) ? $presentedResolutionCase['secondary_actions'] : [];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'question' => __('localization.review.review_pack_output_status'),
|
'question' => __('localization.review.review_pack_output_status'),
|
||||||
'label' => $followUpOverride
|
'label' => $this->workspaceReadinessLabel($effectiveState),
|
||||||
? $this->workspaceReadinessLabel($effectiveState)
|
|
||||||
: (string) ($outputGuidance['label'] ?? $this->workspaceReadinessLabel($effectiveState)),
|
|
||||||
'color' => $followUpOverride
|
'color' => $followUpOverride
|
||||||
? $this->workspaceReadinessColor($effectiveState)
|
? $this->workspaceReadinessColor($effectiveState)
|
||||||
: (string) ($outputGuidance['color'] ?? $this->workspaceReadinessColor($effectiveState)),
|
: (string) ($outputGuidance['color'] ?? $this->workspaceReadinessColor($effectiveState)),
|
||||||
'boundary_label' => $followUpOverride
|
'boundary_label' => $this->workspaceBoundaryLabel((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review')),
|
||||||
? $this->workspaceBoundaryLabel((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review'))
|
|
||||||
: (string) ($outputGuidance['boundary_label'] ?? $this->workspaceBoundaryLabel((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review'))),
|
|
||||||
'boundary_color' => $followUpOverride
|
'boundary_color' => $followUpOverride
|
||||||
? $this->workspaceBoundaryColor((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review'))
|
? $this->workspaceBoundaryColor((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review'))
|
||||||
: (string) ($outputGuidance['boundary_color'] ?? $this->workspaceBoundaryColor((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review'))),
|
: (string) ($outputGuidance['boundary_color'] ?? $this->workspaceBoundaryColor((string) ($outputReadiness['customer_safe_state'] ?? 'requires_review'))),
|
||||||
@ -838,10 +837,10 @@ private function reviewConsumptionFlowForReview(
|
|||||||
|| $workspaceState !== ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY;
|
|| $workspaceState !== ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY;
|
||||||
|
|
||||||
$customerOutputLabel = match (true) {
|
$customerOutputLabel = match (true) {
|
||||||
$hasReadyPackage && ! $hasBlockingAttention => __('localization.review.ready'),
|
$hasReadyPackage && ! $hasBlockingAttention => $this->canonicalCustomerReviewStateLabel('available'),
|
||||||
$workspaceState === ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY || ! $hasReadyPackage => __('localization.review.not_ready'),
|
$workspaceState === ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY || ! $hasReadyPackage => $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
$hasReadyPackage => __('localization.review.needs_review'),
|
$hasReadyPackage => $this->canonicalCustomerReviewStateLabel('limited'),
|
||||||
default => __('localization.review.not_ready'),
|
default => $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
};
|
};
|
||||||
$customerOutputColor = match (true) {
|
$customerOutputColor = match (true) {
|
||||||
$hasReadyPackage && ! $hasBlockingAttention => 'success',
|
$hasReadyPackage && ! $hasBlockingAttention => 'success',
|
||||||
@ -858,7 +857,7 @@ private function reviewConsumptionFlowForReview(
|
|||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
'title' => __('localization.review.review_data'),
|
'title' => __('localization.review.review_data'),
|
||||||
'label' => __('localization.review.available'),
|
'label' => $this->canonicalCustomerReviewStateLabel('available'),
|
||||||
'color' => 'success',
|
'color' => 'success',
|
||||||
'description' => __('localization.review.review_data_available_description'),
|
'description' => __('localization.review.review_data_available_description'),
|
||||||
'is_current' => false,
|
'is_current' => false,
|
||||||
@ -886,7 +885,7 @@ private function reviewConsumptionFlowForReview(
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
'title' => __('localization.review.review_pack'),
|
'title' => __('localization.review.review_pack'),
|
||||||
'label' => $packageAvailability['label'],
|
'label' => $this->canonicalCustomerReviewStateLabel($packageAvailability['state']),
|
||||||
'color' => $this->governancePackageAvailabilityColor($tenant),
|
'color' => $this->governancePackageAvailabilityColor($tenant),
|
||||||
'description' => $this->reviewPackDimensionDescription($packageAvailability),
|
'description' => $this->reviewPackDimensionDescription($packageAvailability),
|
||||||
'is_current' => $packageAvailability['state'] !== 'available',
|
'is_current' => $packageAvailability['state'] !== 'available',
|
||||||
@ -1050,9 +1049,9 @@ private function asideEvidencePathLabel(array $proof): string
|
|||||||
}
|
}
|
||||||
|
|
||||||
return match ($proof['color']) {
|
return match ($proof['color']) {
|
||||||
'success', 'info' => __('localization.review.available'),
|
'success', 'info' => $this->canonicalCustomerReviewStateLabel('available'),
|
||||||
'warning' => __('localization.review.limited'),
|
'warning' => $this->canonicalCustomerReviewStateLabel('limited'),
|
||||||
default => __('localization.review.unavailable'),
|
default => $this->canonicalCustomerReviewStateLabel('unavailable'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1106,11 +1105,11 @@ private function reviewPackProofForReview(array $packageAvailability, ?string $d
|
|||||||
return [
|
return [
|
||||||
'key' => 'review_pack',
|
'key' => 'review_pack',
|
||||||
'title' => __('localization.review.review_pack'),
|
'title' => __('localization.review.review_pack'),
|
||||||
'label' => $packageAvailability['label'],
|
'label' => $this->canonicalCustomerReviewStateLabel($packageAvailability['state']),
|
||||||
'color' => match ($packageAvailability['state']) {
|
'color' => match ($packageAvailability['state']) {
|
||||||
'available' => 'success',
|
'available' => 'success',
|
||||||
'preparing' => 'warning',
|
'preparing' => 'warning',
|
||||||
'expired', 'unavailable' => 'danger',
|
'expired', 'failed', 'unavailable' => 'danger',
|
||||||
default => 'gray',
|
default => 'gray',
|
||||||
},
|
},
|
||||||
'description' => $packageAvailability['description'],
|
'description' => $packageAvailability['description'],
|
||||||
@ -1167,8 +1166,8 @@ private function exportArtifactProofForReview(array $packageAvailability, ?strin
|
|||||||
'key' => 'export_artifact',
|
'key' => 'export_artifact',
|
||||||
'title' => __('localization.review.export_artifact'),
|
'title' => __('localization.review.export_artifact'),
|
||||||
'label' => $downloadUrl !== null
|
'label' => $downloadUrl !== null
|
||||||
? __('localization.review.available')
|
? $this->canonicalCustomerReviewStateLabel('available')
|
||||||
: $packageAvailability['label'],
|
: $this->canonicalCustomerReviewStateLabel($packageAvailability['state']),
|
||||||
'color' => $downloadUrl !== null ? 'success' : 'gray',
|
'color' => $downloadUrl !== null ? 'success' : 'gray',
|
||||||
'description' => $downloadUrl !== null
|
'description' => $downloadUrl !== null
|
||||||
? __('localization.review.export_artifact_available_description')
|
? __('localization.review.export_artifact_available_description')
|
||||||
@ -1211,8 +1210,8 @@ private function findingPanelForReview(ManagedEnvironment $tenant): array
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'status_label' => $open > 0
|
'status_label' => $open > 0
|
||||||
? __('localization.review.needs_review')
|
? $this->canonicalCustomerReviewStateLabel('limited')
|
||||||
: __('localization.review.no_action_needed'),
|
: $this->canonicalCustomerReviewStateLabel('available'),
|
||||||
'status_color' => match (true) {
|
'status_color' => match (true) {
|
||||||
$highImpact > 0 => 'danger',
|
$highImpact > 0 => 'danger',
|
||||||
$open > 0 => 'warning',
|
$open > 0 => 'warning',
|
||||||
@ -1405,7 +1404,7 @@ private function acceptedRiskPanelForReview(EnvironmentReview $review, ManagedEn
|
|||||||
'color' => $pending > 0 ? 'warning' : 'gray',
|
'color' => $pending > 0 ? 'warning' : 'gray',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => __('localization.review.accepted_risks_needs_review'),
|
'label' => $this->canonicalCustomerReviewStateLabel('limited'),
|
||||||
'value' => (string) $needsReview,
|
'value' => (string) $needsReview,
|
||||||
'color' => $needsReview > 0 ? 'warning' : 'gray',
|
'color' => $needsReview > 0 ? 'warning' : 'gray',
|
||||||
],
|
],
|
||||||
@ -1442,9 +1441,9 @@ private function reviewPackPanelForReview(
|
|||||||
default => 'unavailable',
|
default => 'unavailable',
|
||||||
};
|
};
|
||||||
$packageExistsLabel = match ($packageExistsState) {
|
$packageExistsLabel = match ($packageExistsState) {
|
||||||
'available' => __('localization.review.available'),
|
'available' => $this->canonicalCustomerReviewStateLabel('available'),
|
||||||
'preparing' => __('localization.review.preparing'),
|
'preparing' => $this->canonicalCustomerReviewStateLabel('preparing'),
|
||||||
default => __('localization.review.unavailable'),
|
default => $this->canonicalCustomerReviewStateLabel('unavailable'),
|
||||||
};
|
};
|
||||||
$packageExistsColor = match ($packageExistsState) {
|
$packageExistsColor = match ($packageExistsState) {
|
||||||
'available' => 'success',
|
'available' => 'success',
|
||||||
@ -1454,7 +1453,7 @@ private function reviewPackPanelForReview(
|
|||||||
$customerSharingState = (string) ($outputReadiness['customer_safe_state'] ?? 'requires_review');
|
$customerSharingState = (string) ($outputReadiness['customer_safe_state'] ?? 'requires_review');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'status_label' => $packageAvailability['label'],
|
'status_label' => $this->canonicalCustomerReviewStateLabel($packageAvailability['state']),
|
||||||
'status_color' => $this->governancePackageAvailabilityColor($tenant),
|
'status_color' => $this->governancePackageAvailabilityColor($tenant),
|
||||||
'description' => $this->reviewPackPanelDescription($packageAvailability, $outputReadiness),
|
'description' => $this->reviewPackPanelDescription($packageAvailability, $outputReadiness),
|
||||||
'sections' => [
|
'sections' => [
|
||||||
@ -1469,14 +1468,14 @@ private function reviewPackPanelForReview(
|
|||||||
'label' => __('localization.review.last_generated'),
|
'label' => __('localization.review.last_generated'),
|
||||||
'value' => $pack instanceof ReviewPack && $pack->generated_at !== null
|
'value' => $pack instanceof ReviewPack && $pack->generated_at !== null
|
||||||
? $pack->generated_at->format('M j, Y H:i')
|
? $pack->generated_at->format('M j, Y H:i')
|
||||||
: __('localization.review.unavailable'),
|
: $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
'color' => 'gray',
|
'color' => 'gray',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => __('localization.review.evidence_source'),
|
'label' => __('localization.review.evidence_source'),
|
||||||
'value' => $snapshot instanceof EvidenceSnapshot && $snapshot->generated_at !== null
|
'value' => $snapshot instanceof EvidenceSnapshot && $snapshot->generated_at !== null
|
||||||
? $snapshot->generated_at->format('M j, Y H:i')
|
? $snapshot->generated_at->format('M j, Y H:i')
|
||||||
: __('localization.review.unavailable'),
|
: $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
'color' => 'gray',
|
'color' => 'gray',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -1485,8 +1484,8 @@ private function reviewPackPanelForReview(
|
|||||||
'key' => 'internal_export',
|
'key' => 'internal_export',
|
||||||
'title' => __('localization.review.internal_export'),
|
'title' => __('localization.review.internal_export'),
|
||||||
'label' => $downloadUrl !== null
|
'label' => $downloadUrl !== null
|
||||||
? __('localization.review.export_ready')
|
? $this->canonicalCustomerReviewStateLabel('available')
|
||||||
: __('localization.review.export_not_ready'),
|
: $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
'color' => $downloadUrl !== null
|
'color' => $downloadUrl !== null
|
||||||
? 'success'
|
? 'success'
|
||||||
: ($packageAvailability['state'] === 'preparing' ? 'warning' : 'gray'),
|
: ($packageAvailability['state'] === 'preparing' ? 'warning' : 'gray'),
|
||||||
@ -1495,8 +1494,8 @@ private function reviewPackPanelForReview(
|
|||||||
[
|
[
|
||||||
'label' => __('localization.review.export_availability'),
|
'label' => __('localization.review.export_availability'),
|
||||||
'value' => $downloadUrl !== null
|
'value' => $downloadUrl !== null
|
||||||
? __('localization.review.export_ready')
|
? $this->canonicalCustomerReviewStateLabel('available')
|
||||||
: __('localization.review.export_not_ready'),
|
: $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
'color' => $downloadUrl !== null ? 'success' : 'gray',
|
'color' => $downloadUrl !== null ? 'success' : 'gray',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@ -1534,14 +1533,14 @@ private function reviewPackPanelForReview(
|
|||||||
'label' => __('localization.review.protected_values'),
|
'label' => __('localization.review.protected_values'),
|
||||||
'value' => (bool) ($outputReadiness['protected_values_hidden'] ?? true)
|
'value' => (bool) ($outputReadiness['protected_values_hidden'] ?? true)
|
||||||
? __('localization.review.protected_values_hidden')
|
? __('localization.review.protected_values_hidden')
|
||||||
: __('localization.review.unavailable'),
|
: $this->canonicalCustomerReviewStateLabel('blocked'),
|
||||||
'color' => (bool) ($outputReadiness['protected_values_hidden'] ?? true) ? 'success' : 'warning',
|
'color' => (bool) ($outputReadiness['protected_values_hidden'] ?? true) ? 'success' : 'warning',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => __('localization.review.disclosure'),
|
'label' => __('localization.review.disclosure'),
|
||||||
'value' => (bool) ($outputReadiness['disclosure_present'] ?? false)
|
'value' => (bool) ($outputReadiness['disclosure_present'] ?? false)
|
||||||
? __('localization.review.disclosure_present')
|
? __('localization.review.disclosure_present')
|
||||||
: __('localization.review.unavailable'),
|
: $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
'color' => (bool) ($outputReadiness['disclosure_present'] ?? false) ? 'success' : 'warning',
|
'color' => (bool) ($outputReadiness['disclosure_present'] ?? false) ? 'success' : 'warning',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -1595,22 +1594,22 @@ private function disclosureRuleRows(): array
|
|||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
'label' => __('localization.review.disclosure_decision'),
|
'label' => __('localization.review.disclosure_decision'),
|
||||||
'value' => __('localization.review.disclosure_visible'),
|
'value' => $this->canonicalCustomerReviewStateLabel('available'),
|
||||||
'color' => 'info',
|
'color' => 'info',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => __('localization.review.disclosure_evidence'),
|
'label' => __('localization.review.disclosure_evidence'),
|
||||||
'value' => __('localization.review.disclosure_visible'),
|
'value' => $this->canonicalCustomerReviewStateLabel('available'),
|
||||||
'color' => 'info',
|
'color' => 'info',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => __('localization.review.disclosure_diagnostics'),
|
'label' => __('localization.review.disclosure_diagnostics'),
|
||||||
'value' => __('localization.review.disclosure_collapsed'),
|
'value' => $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
'color' => 'gray',
|
'color' => 'gray',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'label' => __('localization.review.disclosure_raw_support'),
|
'label' => __('localization.review.disclosure_raw_support'),
|
||||||
'value' => __('localization.review.disclosure_hidden'),
|
'value' => $this->canonicalCustomerReviewStateLabel('blocked'),
|
||||||
'color' => 'gray',
|
'color' => 'gray',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@ -2017,9 +2016,19 @@ private function latestReviewStateLabel(ManagedEnvironment $tenant): string
|
|||||||
}
|
}
|
||||||
|
|
||||||
return match ($this->workspaceCustomerOutputState($tenant)) {
|
return match ($this->workspaceCustomerOutputState($tenant)) {
|
||||||
'ready' => __('localization.review.ready'),
|
'ready' => $this->canonicalCustomerReviewStateLabel('available'),
|
||||||
'not_ready' => __('localization.review.not_ready'),
|
'not_ready' => $this->latestReviewNotReadyStateLabel($tenant),
|
||||||
default => __('localization.review.needs_review'),
|
default => $this->canonicalCustomerReviewStateLabel('limited'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function latestReviewNotReadyStateLabel(ManagedEnvironment $tenant): string
|
||||||
|
{
|
||||||
|
return match ($this->governancePackageAvailability($tenant)['state']) {
|
||||||
|
'expired' => $this->canonicalCustomerReviewStateLabel('expired'),
|
||||||
|
'failed' => $this->canonicalCustomerReviewStateLabel('failed'),
|
||||||
|
'blocked' => $this->canonicalCustomerReviewStateLabel('blocked'),
|
||||||
|
default => $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2033,7 +2042,7 @@ private function latestReviewStateColor(ManagedEnvironment $tenant): string
|
|||||||
|
|
||||||
return match ($this->workspaceCustomerOutputState($tenant)) {
|
return match ($this->workspaceCustomerOutputState($tenant)) {
|
||||||
'ready' => 'success',
|
'ready' => 'success',
|
||||||
'not_ready' => in_array($this->governancePackageAvailability($tenant)['state'], ['expired', 'unavailable'], true)
|
'not_ready' => in_array($this->governancePackageAvailability($tenant)['state'], ['expired', 'failed', 'blocked'], true)
|
||||||
? 'danger'
|
? 'danger'
|
||||||
: 'gray',
|
: 'gray',
|
||||||
default => 'warning',
|
default => 'warning',
|
||||||
@ -2124,8 +2133,8 @@ private function governancePackageAvailability(ManagedEnvironment $tenant): arra
|
|||||||
|
|
||||||
if (! $review instanceof EnvironmentReview) {
|
if (! $review instanceof EnvironmentReview) {
|
||||||
return [
|
return [
|
||||||
'state' => 'unavailable',
|
'state' => 'not_available',
|
||||||
'label' => __('localization.review.governance_package_unavailable'),
|
'label' => $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
'description' => __('localization.review.no_published_review_available'),
|
'description' => __('localization.review.no_published_review_available'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -2136,15 +2145,15 @@ private function governancePackageAvailability(ManagedEnvironment $tenant): arra
|
|||||||
if (! $pack instanceof ReviewPack) {
|
if (! $pack instanceof ReviewPack) {
|
||||||
return [
|
return [
|
||||||
'state' => 'not_available',
|
'state' => 'not_available',
|
||||||
'label' => __('localization.review.review_pack_not_available_yet'),
|
'label' => $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
'description' => __('localization.review.review_pack_not_available_yet_description'),
|
'description' => __('localization.review.review_pack_not_available_yet_description'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $user instanceof User || ! $user->can(Capabilities::REVIEW_PACK_VIEW, $tenant)) {
|
if (! $user instanceof User || ! $user->can(Capabilities::REVIEW_PACK_VIEW, $tenant)) {
|
||||||
return [
|
return [
|
||||||
'state' => 'unavailable',
|
'state' => 'blocked',
|
||||||
'label' => __('localization.review.unavailable'),
|
'label' => $this->canonicalCustomerReviewStateLabel('blocked'),
|
||||||
'description' => __('localization.review.review_pack_unavailable_customer_description'),
|
'description' => __('localization.review.review_pack_unavailable_customer_description'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -2152,7 +2161,7 @@ private function governancePackageAvailability(ManagedEnvironment $tenant): arra
|
|||||||
if ($pack->status === ReviewPackStatus::Expired->value || ($pack->expires_at !== null && $pack->expires_at->isPast())) {
|
if ($pack->status === ReviewPackStatus::Expired->value || ($pack->expires_at !== null && $pack->expires_at->isPast())) {
|
||||||
return [
|
return [
|
||||||
'state' => 'expired',
|
'state' => 'expired',
|
||||||
'label' => __('localization.review.governance_package_expired'),
|
'label' => $this->canonicalCustomerReviewStateLabel('expired'),
|
||||||
'description' => __('localization.review.governance_package_expired_description'),
|
'description' => __('localization.review.governance_package_expired_description'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -2160,15 +2169,15 @@ private function governancePackageAvailability(ManagedEnvironment $tenant): arra
|
|||||||
if (in_array($pack->status, [ReviewPackStatus::Queued->value, ReviewPackStatus::Generating->value], true)) {
|
if (in_array($pack->status, [ReviewPackStatus::Queued->value, ReviewPackStatus::Generating->value], true)) {
|
||||||
return [
|
return [
|
||||||
'state' => 'preparing',
|
'state' => 'preparing',
|
||||||
'label' => __('localization.review.review_pack_preparing'),
|
'label' => $this->canonicalCustomerReviewStateLabel('preparing'),
|
||||||
'description' => __('localization.review.review_pack_preparing_description'),
|
'description' => __('localization.review.review_pack_preparing_description'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($pack->status !== ReviewPackStatus::Ready->value) {
|
if ($pack->status !== ReviewPackStatus::Ready->value) {
|
||||||
return [
|
return [
|
||||||
'state' => 'unavailable',
|
'state' => 'failed',
|
||||||
'label' => __('localization.review.unavailable'),
|
'label' => $this->canonicalCustomerReviewStateLabel('failed'),
|
||||||
'description' => __('localization.review.review_pack_unavailable_customer_description'),
|
'description' => __('localization.review.review_pack_unavailable_customer_description'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -2176,27 +2185,21 @@ private function governancePackageAvailability(ManagedEnvironment $tenant): arra
|
|||||||
if (! filled($pack->file_path) || ! filled($pack->file_disk)) {
|
if (! filled($pack->file_path) || ! filled($pack->file_disk)) {
|
||||||
return [
|
return [
|
||||||
'state' => 'not_available',
|
'state' => 'not_available',
|
||||||
'label' => __('localization.review.review_pack_not_available_yet'),
|
'label' => $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
'description' => __('localization.review.review_pack_not_available_yet_description'),
|
'description' => __('localization.review.review_pack_not_available_yet_description'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'state' => 'available',
|
'state' => 'available',
|
||||||
'label' => __('localization.review.available'),
|
'label' => $this->canonicalCustomerReviewStateLabel('available'),
|
||||||
'description' => __('localization.review.review_pack_available_customer_description'),
|
'description' => __('localization.review.review_pack_available_customer_description'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function governancePackageAvailabilityLabel(ManagedEnvironment $tenant): string
|
private function governancePackageAvailabilityLabel(ManagedEnvironment $tenant): string
|
||||||
{
|
{
|
||||||
return match ($this->governancePackageAvailability($tenant)['state']) {
|
return (string) $this->governancePackageAvailability($tenant)['label'];
|
||||||
'available' => __('localization.review.available'),
|
|
||||||
'not_available' => __('localization.review.review_pack_not_available_yet'),
|
|
||||||
'preparing' => __('localization.review.review_pack_preparing'),
|
|
||||||
'expired' => __('localization.review.expired'),
|
|
||||||
default => __('localization.review.unavailable'),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function governancePackageAvailabilityColor(ManagedEnvironment $tenant): string
|
private function governancePackageAvailabilityColor(ManagedEnvironment $tenant): string
|
||||||
@ -2204,7 +2207,7 @@ private function governancePackageAvailabilityColor(ManagedEnvironment $tenant):
|
|||||||
return match ($this->governancePackageAvailability($tenant)['state']) {
|
return match ($this->governancePackageAvailability($tenant)['state']) {
|
||||||
'available' => 'success',
|
'available' => 'success',
|
||||||
'preparing' => 'warning',
|
'preparing' => 'warning',
|
||||||
'expired', 'unavailable' => 'danger',
|
'expired', 'failed', 'blocked' => 'danger',
|
||||||
default => 'gray',
|
default => 'gray',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -2389,7 +2392,7 @@ private function evidenceBasisLabel(string $state): string
|
|||||||
'stale' => __('localization.review.evidence_basis_stale'),
|
'stale' => __('localization.review.evidence_basis_stale'),
|
||||||
'incomplete' => __('localization.review.evidence_basis_incomplete'),
|
'incomplete' => __('localization.review.evidence_basis_incomplete'),
|
||||||
'not_generated' => __('localization.review.evidence_basis_not_generated'),
|
'not_generated' => __('localization.review.evidence_basis_not_generated'),
|
||||||
default => __('localization.review.evidence_basis_unavailable'),
|
default => $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2516,13 +2519,34 @@ private function effectiveWorkspaceReadinessState(array $outputReadiness, bool $
|
|||||||
private function workspaceReadinessLabel(string $state): string
|
private function workspaceReadinessLabel(string $state): string
|
||||||
{
|
{
|
||||||
return match ($state) {
|
return match ($state) {
|
||||||
ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY => __('localization.review.customer_safe_review_pack_ready'),
|
ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY => $this->canonicalCustomerReviewStateLabel('available'),
|
||||||
ReviewPackOutputReadiness::STATE_INTERNAL_REVIEW_PACKAGE_AVAILABLE => __('localization.review.internal_review_package_available'),
|
ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY => $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
ReviewPackOutputReadiness::STATE_EXPORT_NOT_READY => __('localization.review.export_not_ready'),
|
default => $this->canonicalCustomerReviewStateLabel('limited'),
|
||||||
default => __('localization.review.published_with_limitations'),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $resolutionCase
|
||||||
|
*/
|
||||||
|
private function canonicalWorkspaceResolutionTitle(array $resolutionCase, string $effectiveState): string
|
||||||
|
{
|
||||||
|
$title = (string) ($resolutionCase['title'] ?? '');
|
||||||
|
$legacyStatusTitles = [
|
||||||
|
__('localization.review.output_not_customer_ready'),
|
||||||
|
__('localization.review.customer_safe_review_pack_ready'),
|
||||||
|
__('localization.review.internal_review_package_available'),
|
||||||
|
__('localization.review.export_not_ready'),
|
||||||
|
__('localization.review.published_with_limitations'),
|
||||||
|
__('localization.review.requires_review'),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($title === '' || in_array($title, $legacyStatusTitles, true)) {
|
||||||
|
return $this->workspaceReadinessLabel($effectiveState);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $title;
|
||||||
|
}
|
||||||
|
|
||||||
private function workspaceReadinessColor(string $state): string
|
private function workspaceReadinessColor(string $state): string
|
||||||
{
|
{
|
||||||
return match ($state) {
|
return match ($state) {
|
||||||
@ -2535,10 +2559,9 @@ private function workspaceReadinessColor(string $state): string
|
|||||||
private function workspaceBoundaryLabel(string $state): string
|
private function workspaceBoundaryLabel(string $state): string
|
||||||
{
|
{
|
||||||
return match ($state) {
|
return match ($state) {
|
||||||
'customer_safe_ready' => __('localization.review.customer_safe'),
|
'customer_safe_ready' => $this->canonicalCustomerReviewStateLabel('available'),
|
||||||
'internal_only' => __('localization.review.internal_only'),
|
'not_ready' => $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
'not_ready' => __('localization.review.not_ready'),
|
default => $this->canonicalCustomerReviewStateLabel('limited'),
|
||||||
default => __('localization.review.requires_review'),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2877,7 +2900,7 @@ private function sectionCompletenessLabel(array $sectionSummary): string
|
|||||||
$requiredLimited = (int) ($sectionSummary['required_limited'] ?? 0);
|
$requiredLimited = (int) ($sectionSummary['required_limited'] ?? 0);
|
||||||
|
|
||||||
if ($requiredTotal <= 0) {
|
if ($requiredTotal <= 0) {
|
||||||
return __('localization.review.unavailable');
|
return $this->canonicalCustomerReviewStateLabel('not_available');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($requiredLimited > 0) {
|
if ($requiredLimited > 0) {
|
||||||
@ -3050,10 +3073,23 @@ private function evidenceStatusState(ManagedEnvironment $tenant): string
|
|||||||
private function evidenceStatusLabelForState(string $state): string
|
private function evidenceStatusLabelForState(string $state): string
|
||||||
{
|
{
|
||||||
return match ($state) {
|
return match ($state) {
|
||||||
'available' => __('localization.review.available'),
|
'available' => $this->canonicalCustomerReviewStateLabel('available'),
|
||||||
'restricted' => __('localization.review.restricted'),
|
'restricted' => $this->canonicalCustomerReviewStateLabel('blocked'),
|
||||||
'expired' => __('localization.review.expired'),
|
'expired' => $this->canonicalCustomerReviewStateLabel('expired'),
|
||||||
default => __('localization.review.pending'),
|
default => $this->canonicalCustomerReviewStateLabel('not_available'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function canonicalCustomerReviewStateLabel(string $state): string
|
||||||
|
{
|
||||||
|
return match ($state) {
|
||||||
|
'available' => 'Ready',
|
||||||
|
'preparing' => 'Running',
|
||||||
|
'failed' => 'Failed',
|
||||||
|
'expired' => 'Expired',
|
||||||
|
'limited' => 'Needs attention',
|
||||||
|
'blocked', 'restricted' => 'Blocked',
|
||||||
|
default => 'Not configured',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -60,10 +60,10 @@ public function currentForScope(Workspace $workspace, ?ManagedEnvironment $envir
|
|||||||
? EvidenceAnchorResult::STATE_NEEDS_ATTENTION
|
? EvidenceAnchorResult::STATE_NEEDS_ATTENTION
|
||||||
: EvidenceAnchorResult::STATE_NOT_CONFIGURED,
|
: EvidenceAnchorResult::STATE_NOT_CONFIGURED,
|
||||||
primaryReason: $hasAnySnapshot
|
primaryReason: $hasAnySnapshot
|
||||||
? 'No complete active evidence snapshot is available for this environment.'
|
? 'No complete active evidence snapshot with usable captured evidence is available for this environment.'
|
||||||
: 'No evidence snapshot is configured for this environment.',
|
: 'No evidence snapshot is configured for this environment.',
|
||||||
blockingReasons: $hasAnySnapshot
|
blockingReasons: $hasAnySnapshot
|
||||||
? ['Only active, complete, non-expired evidence can be used as current evidence.']
|
? ['Only active, complete, non-empty, non-expired evidence can be used as current evidence.']
|
||||||
: ['Generate evidence before opening a current evidence detail.'],
|
: ['Generate evidence before opening a current evidence detail.'],
|
||||||
displayLabel: 'Current evidence',
|
displayLabel: 'Current evidence',
|
||||||
);
|
);
|
||||||
@ -100,11 +100,21 @@ public function currentSnapshotForEnvironment(ManagedEnvironment $environment):
|
|||||||
|
|
||||||
public function currentSnapshotQuery(Workspace $workspace, ManagedEnvironment $environment): Builder
|
public function currentSnapshotQuery(Workspace $workspace, ManagedEnvironment $environment): Builder
|
||||||
{
|
{
|
||||||
return EvidenceSnapshot::query()
|
$query = EvidenceSnapshot::query()
|
||||||
->where('workspace_id', (int) $workspace->getKey())
|
->where('workspace_id', (int) $workspace->getKey())
|
||||||
->where('managed_environment_id', (int) $environment->getKey())
|
->where('managed_environment_id', (int) $environment->getKey())
|
||||||
->where('status', EvidenceSnapshotStatus::Active->value)
|
->where('status', EvidenceSnapshotStatus::Active->value)
|
||||||
->where('completeness_state', EvidenceCompletenessState::Complete->value)
|
->where('completeness_state', EvidenceCompletenessState::Complete->value)
|
||||||
|
->where(function (Builder $query): void {
|
||||||
|
$query
|
||||||
|
->whereNull('summary->missing_dimensions')
|
||||||
|
->orWhere('summary->missing_dimensions', 0);
|
||||||
|
})
|
||||||
|
->where(function (Builder $query): void {
|
||||||
|
$query
|
||||||
|
->whereNull('summary->stale_dimensions')
|
||||||
|
->orWhere('summary->stale_dimensions', 0);
|
||||||
|
})
|
||||||
->where(function (Builder $query): void {
|
->where(function (Builder $query): void {
|
||||||
$query
|
$query
|
||||||
->whereNull('expires_at')
|
->whereNull('expires_at')
|
||||||
@ -113,6 +123,10 @@ public function currentSnapshotQuery(Workspace $workspace, ManagedEnvironment $e
|
|||||||
->orderByRaw('generated_at IS NULL')
|
->orderByRaw('generated_at IS NULL')
|
||||||
->orderByDesc('generated_at')
|
->orderByDesc('generated_at')
|
||||||
->orderByDesc('id');
|
->orderByDesc('id');
|
||||||
|
|
||||||
|
$this->whereHasUsableCapturedDimensions($query);
|
||||||
|
|
||||||
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function forReviewPackDraft(ReviewPack $reviewPack, ?User $actor = null): EvidenceAnchorResult
|
public function forReviewPackDraft(ReviewPack $reviewPack, ?User $actor = null): EvidenceAnchorResult
|
||||||
@ -376,6 +390,14 @@ private function stateForSnapshot(EvidenceSnapshot $snapshot, bool $releaseBound
|
|||||||
return EvidenceAnchorResult::STATE_NEEDS_ATTENTION;
|
return EvidenceAnchorResult::STATE_NEEDS_ATTENTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->hasMissingOrStaleDimensions($snapshot)) {
|
||||||
|
return EvidenceAnchorResult::STATE_NEEDS_ATTENTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->hasNoUsableCapturedEvidence($snapshot)) {
|
||||||
|
return EvidenceAnchorResult::STATE_NEEDS_ATTENTION;
|
||||||
|
}
|
||||||
|
|
||||||
if (! $releaseBound && (string) $snapshot->status !== EvidenceSnapshotStatus::Active->value) {
|
if (! $releaseBound && (string) $snapshot->status !== EvidenceSnapshotStatus::Active->value) {
|
||||||
return EvidenceAnchorResult::STATE_BLOCKED;
|
return EvidenceAnchorResult::STATE_BLOCKED;
|
||||||
}
|
}
|
||||||
@ -397,6 +419,14 @@ private function blockingReasonForSnapshot(EvidenceSnapshot $snapshot, bool $rel
|
|||||||
return 'Evidence completeness requires attention.';
|
return 'Evidence completeness requires attention.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->hasMissingOrStaleDimensions($snapshot)) {
|
||||||
|
return 'Evidence dimensions require attention.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->hasNoUsableCapturedEvidence($snapshot)) {
|
||||||
|
return 'Evidence contains no usable captured content.';
|
||||||
|
}
|
||||||
|
|
||||||
if (! $releaseBound && (string) $snapshot->status !== EvidenceSnapshotStatus::Active->value) {
|
if (! $releaseBound && (string) $snapshot->status !== EvidenceSnapshotStatus::Active->value) {
|
||||||
return 'Evidence is not an active current snapshot.';
|
return 'Evidence is not an active current snapshot.';
|
||||||
}
|
}
|
||||||
@ -415,6 +445,31 @@ private function isExpired(EvidenceSnapshot $snapshot): bool
|
|||||||
|| ($snapshot->expires_at !== null && $snapshot->expires_at->isPast());
|
|| ($snapshot->expires_at !== null && $snapshot->expires_at->isPast());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function hasMissingOrStaleDimensions(EvidenceSnapshot $snapshot): bool
|
||||||
|
{
|
||||||
|
return (int) data_get($snapshot->summary ?? [], 'missing_dimensions', 0) > 0
|
||||||
|
|| (int) data_get($snapshot->summary ?? [], 'stale_dimensions', 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Builder<EvidenceSnapshot> $query
|
||||||
|
*/
|
||||||
|
private function whereHasUsableCapturedDimensions(Builder $query): void
|
||||||
|
{
|
||||||
|
if ($query->getModel()->getConnection()->getDriverName() === 'pgsql') {
|
||||||
|
$query->whereRaw("COALESCE(CASE WHEN jsonb_typeof(summary->'dimension_count') = 'number' THEN (summary->>'dimension_count')::integer ELSE 0 END, 0) > 0");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->where('summary->dimension_count', '>', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasNoUsableCapturedEvidence(EvidenceSnapshot $snapshot): bool
|
||||||
|
{
|
||||||
|
return (int) data_get($snapshot->summary ?? [], 'dimension_count', 0) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
private function snapshotMatchesArtifactScope(EvidenceSnapshot $snapshot, EnvironmentReview|ReviewPack $artifact): bool
|
private function snapshotMatchesArtifactScope(EvidenceSnapshot $snapshot, EnvironmentReview|ReviewPack $artifact): bool
|
||||||
{
|
{
|
||||||
return (int) $snapshot->workspace_id === (int) $artifact->workspace_id
|
return (int) $snapshot->workspace_id === (int) $artifact->workspace_id
|
||||||
|
|||||||
@ -343,6 +343,7 @@ private function buildEvidenceSnapshotEnvelope(EvidenceSnapshot $snapshot): Arti
|
|||||||
$status === 'failed' => 'missing_input',
|
$status === 'failed' => 'missing_input',
|
||||||
$snapshot->completeness_state === 'missing' => 'missing_input',
|
$snapshot->completeness_state === 'missing' => 'missing_input',
|
||||||
$snapshot->completeness_state === 'partial' => 'partial',
|
$snapshot->completeness_state === 'partial' => 'partial',
|
||||||
|
$missingDimensions > 0 => 'partial',
|
||||||
default => 'trusted',
|
default => 'trusted',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -207,7 +207,7 @@ class="rounded-xl border border-gray-200 bg-white p-4 text-sm shadow-sm dark:bor
|
|||||||
data-testid="evidence-disclosure-diagnostics"
|
data-testid="evidence-disclosure-diagnostics"
|
||||||
>
|
>
|
||||||
<summary class="cursor-pointer font-medium text-gray-950 dark:text-white">
|
<summary class="cursor-pointer font-medium text-gray-950 dark:text-white">
|
||||||
Diagnostics - Collapsed
|
Diagnostics
|
||||||
</summary>
|
</summary>
|
||||||
|
|
||||||
<p class="mt-3 text-sm leading-6 text-gray-600 dark:text-gray-300">
|
<p class="mt-3 text-sm leading-6 text-gray-600 dark:text-gray-300">
|
||||||
|
|||||||
@ -307,13 +307,13 @@ class="rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-whit
|
|||||||
<div>
|
<div>
|
||||||
<dt class="font-semibold text-gray-500 dark:text-gray-400">{{ __('localization.review.acknowledged_by') }}</dt>
|
<dt class="font-semibold text-gray-500 dark:text-gray-400">{{ __('localization.review.acknowledged_by') }}</dt>
|
||||||
<dd class="mt-1 font-medium text-gray-900 dark:text-gray-100">
|
<dd class="mt-1 font-medium text-gray-900 dark:text-gray-100">
|
||||||
{{ $acknowledgement['acknowledged_by_label'] ?? __('localization.review.unavailable') }}
|
{{ $acknowledgement['acknowledged_by_label'] ?? 'Not configured' }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dt class="font-semibold text-gray-500 dark:text-gray-400">{{ __('localization.review.acknowledged_at') }}</dt>
|
<dt class="font-semibold text-gray-500 dark:text-gray-400">{{ __('localization.review.acknowledged_at') }}</dt>
|
||||||
<dd class="mt-1 font-medium text-gray-900 dark:text-gray-100">
|
<dd class="mt-1 font-medium text-gray-900 dark:text-gray-100">
|
||||||
{{ $acknowledgement['acknowledged_at_label'] ?? __('localization.review.unavailable') }}
|
{{ $acknowledgement['acknowledged_at_label'] ?? 'Not configured' }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
use App\Models\ProviderConnection;
|
use App\Models\ProviderConnection;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Support\EnvironmentReviewStatus;
|
use App\Support\EnvironmentReviewStatus;
|
||||||
|
use App\Support\Evidence\EvidenceCompletenessState;
|
||||||
use App\Support\Evidence\EvidenceSnapshotStatus;
|
use App\Support\Evidence\EvidenceSnapshotStatus;
|
||||||
use App\Support\OperationRunLinks;
|
use App\Support\OperationRunLinks;
|
||||||
use App\Support\Workspaces\WorkspaceContext;
|
use App\Support\Workspaces\WorkspaceContext;
|
||||||
@ -350,7 +351,8 @@ function spec316BrowserEvidenceSnapshot(ManagedEnvironment $environment): Eviden
|
|||||||
'managed_environment_id' => (int) $environment->getKey(),
|
'managed_environment_id' => (int) $environment->getKey(),
|
||||||
'workspace_id' => (int) $environment->workspace_id,
|
'workspace_id' => (int) $environment->workspace_id,
|
||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'summary' => ['dimension_count' => 5, 'missing_dimensions' => 0, 'stale_dimensions' => 0],
|
||||||
'generated_at' => now(),
|
'generated_at' => now(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,8 @@
|
|||||||
->assertSee('Accepted risks')
|
->assertSee('Accepted risks')
|
||||||
->assertSee('Disclosure rule')
|
->assertSee('Disclosure rule')
|
||||||
->assertSee('Diagnostics')
|
->assertSee('Diagnostics')
|
||||||
->assertSee('Collapsed')
|
->assertSee('Not configured')
|
||||||
|
->assertDontSee('Collapsed')
|
||||||
->assertSee('Raw/support')
|
->assertSee('Raw/support')
|
||||||
->assertSee('Review package index')
|
->assertSee('Review package index')
|
||||||
->assertSee('Decision trail')
|
->assertSee('Decision trail')
|
||||||
|
|||||||
@ -137,7 +137,7 @@ function spec337BrowserEnvironmentFor(User $user, ManagedEnvironment $baseEnviro
|
|||||||
->assertScript('document.querySelectorAll("[data-testid=\"evidence-readiness-step\"]").length === 6', true)
|
->assertScript('document.querySelectorAll("[data-testid=\"evidence-readiness-step\"]").length === 6', true)
|
||||||
->assertScript('(() => { const list = document.querySelector("[data-testid=\"evidence-readiness-flow\"] ol"); return list !== null && getComputedStyle(list).flexDirection === "column"; })()', true)
|
->assertScript('(() => { const list = document.querySelector("[data-testid=\"evidence-readiness-flow\"] ol"); return list !== null && getComputedStyle(list).flexDirection === "column"; })()', true)
|
||||||
->assertScript('(() => { const steps = Array.from(document.querySelectorAll("[data-testid=\"evidence-readiness-step\"]")); return steps.length === 6 && steps.every((step) => step.getBoundingClientRect().width >= 300); })()', true)
|
->assertScript('(() => { const steps = Array.from(document.querySelectorAll("[data-testid=\"evidence-readiness-step\"]")); return steps.length === 6 && steps.every((step) => step.getBoundingClientRect().width >= 300); })()', true)
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepState === "Missing"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepState === "Not configured"', true)
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
||||||
->assertScript('document.querySelector("[data-testid=\"evidence-disclosure-diagnostics\"]")?.open === false', true)
|
->assertScript('document.querySelector("[data-testid=\"evidence-disclosure-diagnostics\"]")?.open === false', true)
|
||||||
->assertDontSee('raw payload should stay hidden')
|
->assertDontSee('raw payload should stay hidden')
|
||||||
@ -153,7 +153,7 @@ function spec337BrowserEnvironmentFor(User $user, ManagedEnvironment $baseEnviro
|
|||||||
->waitForText('Evidence snapshot required')
|
->waitForText('Evidence snapshot required')
|
||||||
->assertDontSee('View operation progress')
|
->assertDontSee('View operation progress')
|
||||||
->assertDontSee('Spec337 Browser Operator')
|
->assertDontSee('Spec337 Browser Operator')
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepState === "Missing"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepState === "Not configured"', true)
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
->assertNoConsoleLogs();
|
->assertNoConsoleLogs();
|
||||||
@ -166,7 +166,7 @@ function spec337BrowserEnvironmentFor(User $user, ManagedEnvironment $baseEnviro
|
|||||||
->waitForText('Stored report required')
|
->waitForText('Stored report required')
|
||||||
->assertSee('Current evidence')
|
->assertSee('Current evidence')
|
||||||
->assertDontSee('Open evidence snapshot')
|
->assertDontSee('Open evidence snapshot')
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Stored report\"]")?.dataset.stepState === "Missing"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Stored report\"]")?.dataset.stepState === "Not configured"', true)
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Stored report\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Stored report\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
||||||
->assertDontSee('Download export')
|
->assertDontSee('Download export')
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
@ -179,7 +179,7 @@ function spec337BrowserEnvironmentFor(User $user, ManagedEnvironment $baseEnviro
|
|||||||
]))
|
]))
|
||||||
->waitForText('Review pack required')
|
->waitForText('Review pack required')
|
||||||
->assertSee('Generate review pack')
|
->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.stepState === "Not configured"', true)
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Review pack\"]")?.dataset.stepCurrentBlocker === "true"', 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)
|
->assertScript('document.querySelector("[data-testid=\"evidence-primary-proof-action\"]") instanceof HTMLAnchorElement', true)
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
@ -194,9 +194,9 @@ function spec337BrowserEnvironmentFor(User $user, ManagedEnvironment $baseEnviro
|
|||||||
->assertSee('Download export')
|
->assertSee('Download export')
|
||||||
->assertSee('Customer-safe output')
|
->assertSee('Customer-safe output')
|
||||||
->assertSee('Review pack contents / coverage')
|
->assertSee('Review pack contents / coverage')
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Review pack\"]")?.dataset.stepState === "Available"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Review pack\"]")?.dataset.stepState === "Ready"', true)
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Customer-safe output\"]")?.dataset.stepState === "Ready"', 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('document.querySelector("[data-step-label=\"Export / delivery\"]")?.dataset.stepState === "Ready"', 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)
|
->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('Auditor-ready')
|
||||||
->assertDontSee('environment is healthy')
|
->assertDontSee('environment is healthy')
|
||||||
@ -213,7 +213,7 @@ function spec337BrowserEnvironmentFor(User $user, ManagedEnvironment $baseEnviro
|
|||||||
]))
|
]))
|
||||||
->waitForText('Export unavailable')
|
->waitForText('Export unavailable')
|
||||||
->assertSee('Open review pack')
|
->assertSee('Open review pack')
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Export / delivery\"]")?.dataset.stepState === "Required"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Export / delivery\"]")?.dataset.stepState === "Needs attention"', true)
|
||||||
->assertDontSee('Download export')
|
->assertDontSee('Download export')
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
->assertNoConsoleLogs();
|
->assertNoConsoleLogs();
|
||||||
|
|||||||
@ -108,14 +108,16 @@
|
|||||||
|
|
||||||
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($notReadyEnvironment))
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($notReadyEnvironment))
|
||||||
->resize(1236, 900)
|
->resize(1236, 900)
|
||||||
->waitForText('Output not customer-ready')
|
->waitForText('What is the current review pack output state?')
|
||||||
|
->assertSee('Needs attention')
|
||||||
->assertSee('Review blockers are still recorded for this output.')
|
->assertSee('Review blockers are still recorded for this output.')
|
||||||
->assertSee('Needs review')
|
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
|
->assertDontSee('Output not customer-ready')
|
||||||
->assertSee('Review consumption flow')
|
->assertSee('Review consumption flow')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Needs attention"', true)
|
||||||
->assertScript('document.querySelectorAll("[data-testid=\"customer-review-readiness-step\"]").length === 6', true)
|
->assertScript('document.querySelectorAll("[data-testid=\"customer-review-readiness-step\"]").length === 6', true)
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Review pack\"]")?.dataset.stepState === "Available"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Review pack\"]")?.dataset.stepState === "Ready"', true)
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Customer-safe output\"]")?.dataset.stepState === "Not ready"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Customer-safe output\"]")?.dataset.stepState === "Not configured"', true)
|
||||||
->assertScript('document.querySelector("[data-testid=\"customer-review-diagnostics\"]")?.open === false', true)
|
->assertScript('document.querySelector("[data-testid=\"customer-review-diagnostics\"]")?.open === false', true)
|
||||||
->assertDontSee('raw payload should stay hidden')
|
->assertDontSee('raw payload should stay hidden')
|
||||||
->assertDontSee('provider response should stay hidden')
|
->assertDontSee('provider response should stay hidden')
|
||||||
@ -127,14 +129,17 @@
|
|||||||
spec342CopyBrowserScreenshot('01-evidence-incomplete-not-ready');
|
spec342CopyBrowserScreenshot('01-evidence-incomplete-not-ready');
|
||||||
|
|
||||||
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($readyEnvironment))
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($readyEnvironment))
|
||||||
->waitForText('Customer-safe review pack ready')
|
->waitForText('What is the current review pack output state?')
|
||||||
|
->assertSee('Ready')
|
||||||
->assertSee('Stakeholders can use the current review pack and released review as the evidence path.')
|
->assertSee('Stakeholders can use the current review pack and released review as the evidence path.')
|
||||||
->assertSee('Download customer-safe review pack')
|
->assertSee('Download customer-safe review pack')
|
||||||
->assertSee('Review pack state')
|
->assertSee('Review pack state')
|
||||||
->assertSee('Export ready')
|
->assertSee('Ready')
|
||||||
|
->assertDontSee('Customer-safe review pack ready')
|
||||||
->assertDontSee('Operation proof')
|
->assertDontSee('Operation proof')
|
||||||
->assertDontSee('Spec342 Browser Operator')
|
->assertDontSee('Spec342 Browser Operator')
|
||||||
->assertSee('No open findings require customer action.')
|
->assertSee('No open findings require customer action.')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Ready"', true)
|
||||||
->assertScript('document.querySelectorAll("[data-testid=\"customer-review-primary-action\"]").length === 1', true)
|
->assertScript('document.querySelectorAll("[data-testid=\"customer-review-primary-action\"]").length === 1', true)
|
||||||
->assertScript('document.querySelector("[data-testid=\"customer-review-evidence-path-panel\"]")?.innerText.includes("Download review pack") === false', true)
|
->assertScript('document.querySelector("[data-testid=\"customer-review-evidence-path-panel\"]")?.innerText.includes("Download review pack") === false', true)
|
||||||
->assertScript('document.querySelector("[data-testid=\"customer-review-secondary-action\"]")?.innerText.includes("Download customer-safe review pack") === false', true)
|
->assertScript('document.querySelector("[data-testid=\"customer-review-secondary-action\"]")?.innerText.includes("Download customer-safe review pack") === false', true)
|
||||||
@ -154,13 +159,13 @@
|
|||||||
$page->script('document.querySelector("[data-testid=\"customer-review-review-pack-panel\"]")?.scrollIntoView({ block: "center" });');
|
$page->script('document.querySelector("[data-testid=\"customer-review-review-pack-panel\"]")?.scrollIntoView({ block: "center" });');
|
||||||
$page
|
$page
|
||||||
->assertSee('Export availability')
|
->assertSee('Export availability')
|
||||||
->assertSee('Available');
|
->assertSee('Ready');
|
||||||
$page->screenshot(true, spec342BrowserScreenshotName('03-review-pack-available'));
|
$page->screenshot(true, spec342BrowserScreenshotName('03-review-pack-available'));
|
||||||
spec342CopyBrowserScreenshot('03-review-pack-available');
|
spec342CopyBrowserScreenshot('03-review-pack-available');
|
||||||
|
|
||||||
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($findingsEnvironment))
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($findingsEnvironment))
|
||||||
->waitForText('Findings needing attention')
|
->waitForText('Findings needing attention')
|
||||||
->assertSee('Published with limitations')
|
->assertSee('Needs attention')
|
||||||
->assertSee('1 open finding needs attention; 1 is high impact.')
|
->assertSee('1 open finding needs attention; 1 is high impact.')
|
||||||
->assertSee('Keep open findings visible before customer handoff.')
|
->assertSee('Keep open findings visible before customer handoff.')
|
||||||
->assertSee('Do not treat this review as share-ready until open findings are resolved, accepted, or explicitly reviewed.')
|
->assertSee('Do not treat this review as share-ready until open findings are resolved, accepted, or explicitly reviewed.')
|
||||||
@ -169,16 +174,16 @@
|
|||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"]")?.innerText.includes("Download internal preview") === false', true)
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"]")?.innerText.includes("Download internal preview") === false', true)
|
||||||
->assertScript('document.querySelector("[data-testid=\"customer-review-evidence-path-panel\"]")?.innerText.includes("Download review pack") === false', true)
|
->assertScript('document.querySelector("[data-testid=\"customer-review-evidence-path-panel\"]")?.innerText.includes("Download review pack") === false', true)
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Findings triaged\"]")?.dataset.stepState === "Needs review"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Findings triaged\"]")?.dataset.stepState === "Needs attention"', true)
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Findings triaged\"]")?.dataset.stepCurrent === "true"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Findings triaged\"]")?.dataset.stepCurrent === "true"', true)
|
||||||
->assertScript('document.querySelector("[data-step-label=\"Customer-safe output\"]")?.dataset.stepState === "Needs review"', true)
|
->assertScript('document.querySelector("[data-step-label=\"Customer-safe output\"]")?.dataset.stepState === "Needs attention"', true)
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
->assertNoConsoleLogs();
|
->assertNoConsoleLogs();
|
||||||
$page->screenshot(true, spec342BrowserScreenshotName('04-findings-need-attention'));
|
$page->screenshot(true, spec342BrowserScreenshotName('04-findings-need-attention'));
|
||||||
spec342CopyBrowserScreenshot('04-findings-need-attention');
|
spec342CopyBrowserScreenshot('04-findings-need-attention');
|
||||||
|
|
||||||
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($acceptedRiskEnvironment))
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($acceptedRiskEnvironment))
|
||||||
->waitForText('Published with limitations')
|
->waitForText('Needs attention')
|
||||||
->assertSee('Accepted-risk follow-up is recorded for this review. Review the owner, rationale, and review date before handoff.')
|
->assertSee('Accepted-risk follow-up is recorded for this review. Review the owner, rationale, and review date before handoff.')
|
||||||
->assertSee('The pack can be shared only with the accepted-risk context included in the customer handoff.')
|
->assertSee('The pack can be shared only with the accepted-risk context included in the customer handoff.')
|
||||||
->assertSee('Open review')
|
->assertSee('Open review')
|
||||||
|
|||||||
@ -82,9 +82,12 @@
|
|||||||
|
|
||||||
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($readyEnvironment))
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($readyEnvironment))
|
||||||
->resize(1236, 900)
|
->resize(1236, 900)
|
||||||
->waitForText('Customer-safe review pack ready')
|
->waitForText('What is the current review pack output state?')
|
||||||
|
->assertSee('Ready')
|
||||||
->assertSee('Download customer-safe review pack')
|
->assertSee('Download customer-safe review pack')
|
||||||
->assertSee('PII excluded')
|
->assertSee('PII excluded')
|
||||||
|
->assertDontSee('Customer-safe review pack ready')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Ready"', true)
|
||||||
->assertDontSee('Ready to share')
|
->assertDontSee('Ready to share')
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
->assertNoConsoleLogs();
|
->assertNoConsoleLogs();
|
||||||
@ -92,22 +95,28 @@
|
|||||||
spec347CopyBrowserScreenshot('01-customer-safe-ready');
|
spec347CopyBrowserScreenshot('01-customer-safe-ready');
|
||||||
|
|
||||||
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($limitedEnvironment))
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($limitedEnvironment))
|
||||||
->waitForText('Output not customer-ready')
|
->waitForText('What is the current review pack output state?')
|
||||||
|
->assertSee('Needs attention')
|
||||||
->assertSee('Review blockers are still recorded for this output.')
|
->assertSee('Review blockers are still recorded for this output.')
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
->assertSee('Requires review')
|
->assertDontSee('Output not customer-ready')
|
||||||
|
->assertDontSee('Requires review')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Needs attention"', true)
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
->assertNoConsoleLogs();
|
->assertNoConsoleLogs();
|
||||||
$page->screenshot(true, spec347BrowserScreenshotName('02-published-with-limitations'));
|
$page->screenshot(true, spec347BrowserScreenshotName('02-published-with-limitations'));
|
||||||
spec347CopyBrowserScreenshot('02-published-with-limitations');
|
spec347CopyBrowserScreenshot('02-published-with-limitations');
|
||||||
|
|
||||||
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($internalEnvironment))
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($internalEnvironment))
|
||||||
->waitForText('Internal review package available')
|
->waitForText('What is the current review pack output state?')
|
||||||
|
->assertSee('Needs attention')
|
||||||
->assertSee('Contains PII')
|
->assertSee('Contains PII')
|
||||||
->assertSee('Review PII/redaction state')
|
->assertSee('Review PII/redaction state')
|
||||||
->assertDontSee('Download internal review pack')
|
->assertDontSee('Download internal review pack')
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
->assertSee('Internal only')
|
->assertDontSee('Internal only')
|
||||||
|
->assertDontSee('Internal review package available')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Needs attention"', true)
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
->assertNoConsoleLogs();
|
->assertNoConsoleLogs();
|
||||||
$page->screenshot(true, spec347BrowserScreenshotName('03-internal-review-package'));
|
$page->screenshot(true, spec347BrowserScreenshotName('03-internal-review-package'));
|
||||||
|
|||||||
@ -84,13 +84,16 @@
|
|||||||
|
|
||||||
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($blockedEnvironment))
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($blockedEnvironment))
|
||||||
->resize(1236, 900)
|
->resize(1236, 900)
|
||||||
->waitForText('Output not customer-ready')
|
->waitForText('What is the current review pack output state?')
|
||||||
|
->assertSee('Needs attention')
|
||||||
->assertSee('Create next review')
|
->assertSee('Create next review')
|
||||||
->assertSee('Inspect review blockers')
|
->assertSee('Inspect review blockers')
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
->assertSee('Create the next review cycle from the latest eligible evidence basis.')
|
->assertSee('Create the next review cycle from the latest eligible evidence basis.')
|
||||||
->assertScript('Array.from(document.querySelectorAll("[data-testid=\"customer-review-decision-card\"] [data-testid=\"customer-review-secondary-action\"]")).some((element) => element.innerText.includes("Open review")) === false', true)
|
->assertScript('Array.from(document.querySelectorAll("[data-testid=\"customer-review-decision-card\"] [data-testid=\"customer-review-secondary-action\"]")).some((element) => element.innerText.includes("Open review")) === false', true)
|
||||||
->assertSee('Requires review')
|
->assertDontSee('Output not customer-ready')
|
||||||
|
->assertDontSee('Requires review')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Needs attention"', true)
|
||||||
->assertScript('document.querySelector("[data-testid=\"customer-review-output-limitations\"]")?.open === false', true)
|
->assertScript('document.querySelector("[data-testid=\"customer-review-output-limitations\"]")?.open === false', true)
|
||||||
->assertScript('document.querySelector("[data-testid=\"customer-review-technical-details\"]") === null', true)
|
->assertScript('document.querySelector("[data-testid=\"customer-review-technical-details\"]") === null', true)
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
@ -99,19 +102,25 @@
|
|||||||
spec349CopyBrowserScreenshot('01-output-blocked');
|
spec349CopyBrowserScreenshot('01-output-blocked');
|
||||||
|
|
||||||
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($internalEnvironment))
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($internalEnvironment))
|
||||||
->waitForText('Internal review package available')
|
->waitForText('What is the current review pack output state?')
|
||||||
|
->assertSee('Needs attention')
|
||||||
->assertSee('Review PII/redaction state')
|
->assertSee('Review PII/redaction state')
|
||||||
->assertDontSee('Download internal review pack')
|
->assertDontSee('Download internal review pack')
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
->assertSee('Internal only')
|
->assertDontSee('Internal only')
|
||||||
|
->assertDontSee('Internal review package available')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Needs attention"', true)
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
->assertNoConsoleLogs();
|
->assertNoConsoleLogs();
|
||||||
$page->screenshot(true, spec349BrowserScreenshotName('02-internal-only'));
|
$page->screenshot(true, spec349BrowserScreenshotName('02-internal-only'));
|
||||||
spec349CopyBrowserScreenshot('02-internal-only');
|
spec349CopyBrowserScreenshot('02-internal-only');
|
||||||
|
|
||||||
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($readyEnvironment))
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($readyEnvironment))
|
||||||
->waitForText('Customer-safe review pack ready')
|
->waitForText('What is the current review pack output state?')
|
||||||
|
->assertSee('Ready')
|
||||||
->assertSee('Download customer-safe review pack')
|
->assertSee('Download customer-safe review pack')
|
||||||
|
->assertDontSee('Customer-safe review pack ready')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Ready"', true)
|
||||||
->assertDontSee('Ready to share')
|
->assertDontSee('Ready to share')
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
->assertNoConsoleLogs();
|
->assertNoConsoleLogs();
|
||||||
|
|||||||
@ -55,10 +55,12 @@
|
|||||||
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($environment))
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($environment))
|
||||||
->resize(1236, 900)
|
->resize(1236, 900)
|
||||||
->waitForText('What is the current review pack output state?')
|
->waitForText('What is the current review pack output state?')
|
||||||
->assertSee('Output not customer-ready')
|
->assertSee('Needs attention')
|
||||||
->assertSee('Create next review')
|
->assertSee('Create next review')
|
||||||
->assertSee('Evidence basis incomplete')
|
->assertSee('Evidence basis incomplete')
|
||||||
->assertDontSee('Technical details')
|
->assertDontSee('Technical details')
|
||||||
|
->assertDontSee('Output not customer-ready')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Needs attention"', true)
|
||||||
->click('[data-testid="customer-review-primary-action"]')
|
->click('[data-testid="customer-review-primary-action"]')
|
||||||
->waitForText('Create next review?')
|
->waitForText('Create next review?')
|
||||||
->assertSee('Create next review?')
|
->assertSee('Create next review?')
|
||||||
|
|||||||
@ -62,16 +62,47 @@
|
|||||||
'published_by_user_id' => null,
|
'published_by_user_id' => null,
|
||||||
])->save();
|
])->save();
|
||||||
|
|
||||||
|
$readySnapshot = seedEnvironmentReviewEvidence($readyDraftEnvironment, findingCount: 0, driftCount: 0);
|
||||||
$readyReview = markEnvironmentReviewCustomerSafeReady(composeEnvironmentReviewForTest(
|
$readyReview = markEnvironmentReviewCustomerSafeReady(composeEnvironmentReviewForTest(
|
||||||
$readyDraftEnvironment,
|
$readyDraftEnvironment,
|
||||||
$user,
|
$user,
|
||||||
seedEnvironmentReviewEvidence($readyDraftEnvironment, findingCount: 0, driftCount: 0),
|
$readySnapshot,
|
||||||
));
|
));
|
||||||
$readyReview->forceFill([
|
$readyReview->forceFill([
|
||||||
'status' => EnvironmentReviewStatus::Ready->value,
|
'status' => EnvironmentReviewStatus::Ready->value,
|
||||||
'published_at' => null,
|
'published_at' => null,
|
||||||
'published_by_user_id' => null,
|
'published_by_user_id' => null,
|
||||||
])->save();
|
])->save();
|
||||||
|
$readyReview->sections()->get()->each(function ($section): void {
|
||||||
|
$summary = is_array($section->summary_payload) ? $section->summary_payload : [];
|
||||||
|
$baselineReadiness = is_array($summary['baseline_readiness'] ?? null) ? $summary['baseline_readiness'] : [];
|
||||||
|
|
||||||
|
$baselineReadiness['publication_blockers'] = [];
|
||||||
|
$summary['publication_blockers'] = [];
|
||||||
|
$summary['baseline_readiness'] = $baselineReadiness;
|
||||||
|
|
||||||
|
$section->forceFill([
|
||||||
|
'completeness_state' => EnvironmentReviewCompletenessState::Complete->value,
|
||||||
|
'summary_payload' => $summary,
|
||||||
|
])->save();
|
||||||
|
});
|
||||||
|
$readyReview = $readyReview->refresh();
|
||||||
|
Storage::disk('exports')->put('review-packs/spec351-browser-ready.zip', 'PK-spec351-ready');
|
||||||
|
$readyPack = ReviewPack::factory()->ready()->create([
|
||||||
|
'managed_environment_id' => (int) $readyDraftEnvironment->getKey(),
|
||||||
|
'workspace_id' => (int) $readyDraftEnvironment->workspace_id,
|
||||||
|
'environment_review_id' => (int) $readyReview->getKey(),
|
||||||
|
'evidence_snapshot_id' => (int) $readySnapshot->getKey(),
|
||||||
|
'initiated_by_user_id' => (int) $user->getKey(),
|
||||||
|
'options' => [
|
||||||
|
'include_pii' => false,
|
||||||
|
'include_operations' => true,
|
||||||
|
],
|
||||||
|
'file_path' => 'review-packs/spec351-browser-ready.zip',
|
||||||
|
'file_disk' => 'exports',
|
||||||
|
'generated_at' => now()->subMinutes(3),
|
||||||
|
]);
|
||||||
|
$readyReview->forceFill(['current_export_review_pack_id' => (int) $readyPack->getKey()])->save();
|
||||||
|
|
||||||
spec351BrowserCreatePublishedReviewWithPack(
|
spec351BrowserCreatePublishedReviewWithPack(
|
||||||
$fallbackEnvironment,
|
$fallbackEnvironment,
|
||||||
@ -91,11 +122,14 @@
|
|||||||
|
|
||||||
$workspacePage = visit(CustomerReviewWorkspace::environmentFilterUrl($publishedBlockedEnvironment))
|
$workspacePage = visit(CustomerReviewWorkspace::environmentFilterUrl($publishedBlockedEnvironment))
|
||||||
->resize(1236, 900)
|
->resize(1236, 900)
|
||||||
->waitForText('Output not customer-ready')
|
->waitForText('What is the current review pack output state?')
|
||||||
|
->assertSee('Needs attention')
|
||||||
->assertSee('Create next review')
|
->assertSee('Create next review')
|
||||||
->assertSee('Supporting actions')
|
->assertSee('Supporting actions')
|
||||||
->assertSee('Package exists')
|
->assertSee('Package exists')
|
||||||
->assertSee('Customer sharing')
|
->assertSee('Customer sharing')
|
||||||
|
->assertDontSee('Output not customer-ready')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Needs attention"', true)
|
||||||
->click('[data-testid="customer-review-primary-action"]')
|
->click('[data-testid="customer-review-primary-action"]')
|
||||||
->waitForText('Create next review?')
|
->waitForText('Create next review?')
|
||||||
->assertSee('Create next review?')
|
->assertSee('Create next review?')
|
||||||
@ -117,10 +151,11 @@
|
|||||||
spec351CopyBrowserScreenshot('01b-workspace-successor');
|
spec351CopyBrowserScreenshot('01b-workspace-successor');
|
||||||
|
|
||||||
$mutableBlockedPage = visit(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $mutableBlockedReview], $mutableBlockedEnvironment))
|
$mutableBlockedPage = visit(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $mutableBlockedReview], $mutableBlockedEnvironment))
|
||||||
->waitForText('Refresh review')
|
->waitForText('Resolve publication blockers')
|
||||||
->click('Refresh review')
|
->click('Resolve publication blockers')
|
||||||
->waitForText('Refresh review')
|
->waitForText('Publication preparation')
|
||||||
->assertSee('Refresh this environment review from the latest eligible evidence basis.')
|
->assertSee('Why publication is blocked')
|
||||||
|
->assertSee('Next safe action')
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
->assertNoConsoleLogs();
|
->assertNoConsoleLogs();
|
||||||
$mutableBlockedPage->screenshot(true, spec351BrowserScreenshotName('02-mutable-blocked'));
|
$mutableBlockedPage->screenshot(true, spec351BrowserScreenshotName('02-mutable-blocked'));
|
||||||
@ -139,9 +174,12 @@
|
|||||||
spec351AuthenticateBrowser($this, $readonlyUser, $fallbackEnvironment);
|
spec351AuthenticateBrowser($this, $readonlyUser, $fallbackEnvironment);
|
||||||
|
|
||||||
$fallbackPage = visit(CustomerReviewWorkspace::environmentFilterUrl($fallbackEnvironment))
|
$fallbackPage = visit(CustomerReviewWorkspace::environmentFilterUrl($fallbackEnvironment))
|
||||||
->waitForText('Output not customer-ready')
|
->waitForText('What is the current review pack output state?')
|
||||||
|
->assertSee('Needs attention')
|
||||||
->assertSee('Inspect review blockers')
|
->assertSee('Inspect review blockers')
|
||||||
->assertDontSee('Create next review')
|
->assertDontSee('Create next review')
|
||||||
|
->assertDontSee('Output not customer-ready')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Needs attention"', true)
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
->assertNoConsoleLogs();
|
->assertNoConsoleLogs();
|
||||||
$fallbackPage->screenshot(true, spec351BrowserScreenshotName('04-fallback'));
|
$fallbackPage->screenshot(true, spec351BrowserScreenshotName('04-fallback'));
|
||||||
|
|||||||
@ -66,8 +66,11 @@
|
|||||||
|
|
||||||
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($environment))
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($environment))
|
||||||
->resize(1366, 920)
|
->resize(1366, 920)
|
||||||
->waitForText('Output not customer-ready')
|
->waitForText('What is the current review pack output state?')
|
||||||
|
->assertSee('Needs attention')
|
||||||
->assertSee('Review blockers are still recorded for this output.')
|
->assertSee('Review blockers are still recorded for this output.')
|
||||||
|
->assertDontSee('Output not customer-ready')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Needs attention"', true)
|
||||||
->assertScript('document.querySelector("[data-testid=\"customer-review-output-limitations\"]")?.open === false', true)
|
->assertScript('document.querySelector("[data-testid=\"customer-review-output-limitations\"]")?.open === false', true)
|
||||||
->click('[data-testid="customer-review-output-limitations"] summary')
|
->click('[data-testid="customer-review-output-limitations"] summary')
|
||||||
->assertSee('Baseline readiness blocked')
|
->assertSee('Baseline readiness blocked')
|
||||||
|
|||||||
@ -42,18 +42,24 @@
|
|||||||
|
|
||||||
visit(CustomerReviewWorkspace::environmentFilterUrl($readyEnvironment))
|
visit(CustomerReviewWorkspace::environmentFilterUrl($readyEnvironment))
|
||||||
->resize(1236, 900)
|
->resize(1236, 900)
|
||||||
->waitForText('Customer-safe review pack ready')
|
->waitForText('What is the current review pack output state?')
|
||||||
|
->assertSee('Ready')
|
||||||
->assertSee('Download customer-safe review pack')
|
->assertSee('Download customer-safe review pack')
|
||||||
|
->assertDontSee('Customer-safe review pack ready')
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Ready"', true)
|
||||||
->assertScript('document.querySelector("[data-testid=\"customer-review-primary-action\"]")?.innerText.includes("Download customer-safe review pack") === true', true)
|
->assertScript('document.querySelector("[data-testid=\"customer-review-primary-action\"]")?.innerText.includes("Download customer-safe review pack") === true', true)
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
->assertNoConsoleLogs();
|
->assertNoConsoleLogs();
|
||||||
|
|
||||||
visit(CustomerReviewWorkspace::environmentFilterUrl($blockedEnvironment))
|
visit(CustomerReviewWorkspace::environmentFilterUrl($blockedEnvironment))
|
||||||
->resize(1236, 900)
|
->resize(1236, 900)
|
||||||
->waitForText('Output not customer-ready')
|
->waitForText('What is the current review pack output state?')
|
||||||
->assertSee('Requires review')
|
->assertSee('Needs attention')
|
||||||
|
->assertDontSee('Output not customer-ready')
|
||||||
|
->assertDontSee('Requires review')
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"] h2")?.textContent.trim() === "Needs attention"', true)
|
||||||
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"]")?.innerText.includes("Download internal preview") === false', true)
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"]")?.innerText.includes("Download internal preview") === false', true)
|
||||||
->assertScript('document.querySelector("[data-testid=\"customer-review-primary-action\"]")?.innerText.includes("Download customer-safe review pack") === false', true)
|
->assertScript('document.querySelector("[data-testid=\"customer-review-primary-action\"]")?.innerText.includes("Download customer-safe review pack") === false', true)
|
||||||
->assertNoJavaScriptErrors()
|
->assertNoJavaScriptErrors()
|
||||||
|
|||||||
@ -0,0 +1,248 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Models\EvidenceSnapshot;
|
||||||
|
use App\Models\ManagedEnvironment;
|
||||||
|
use App\Models\OperationRun;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Support\Evidence\EvidenceCompletenessState;
|
||||||
|
use App\Support\Evidence\EvidenceSnapshotStatus;
|
||||||
|
use App\Support\OperationRunLinks;
|
||||||
|
use App\Support\OperationRunOutcome;
|
||||||
|
use App\Support\OperationRunStatus;
|
||||||
|
use App\Support\Workspaces\WorkspaceContext;
|
||||||
|
|
||||||
|
pest()->browser()->timeout(60_000);
|
||||||
|
|
||||||
|
it('Spec403 smokes Evidence Overview canonical operation proof currentness labels', function (): void {
|
||||||
|
$environment = ManagedEnvironment::factory()->active()->create([
|
||||||
|
'name' => 'Spec403 Failed Proof Environment',
|
||||||
|
]);
|
||||||
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
|
||||||
|
$foreignEnvironment = ManagedEnvironment::factory()->active()->create([
|
||||||
|
'name' => 'Spec403 Foreign Evidence Environment',
|
||||||
|
]);
|
||||||
|
$expiredEnvironment = spec403BrowserEnvironmentFor($user, $environment, 'Spec403 Expired Evidence Environment');
|
||||||
|
$staleDimensionEnvironment = spec403BrowserEnvironmentFor($user, $environment, 'Spec403 Stale Dimension Environment');
|
||||||
|
$missingDimensionEnvironment = spec403BrowserEnvironmentFor($user, $environment, 'Spec403 Missing Dimension Environment');
|
||||||
|
|
||||||
|
$run = OperationRun::factory()->forTenant($environment)->create([
|
||||||
|
'status' => OperationRunStatus::Completed->value,
|
||||||
|
'outcome' => OperationRunOutcome::Failed->value,
|
||||||
|
'started_at' => now()->subMinutes(10),
|
||||||
|
'completed_at' => now()->subMinutes(9),
|
||||||
|
'initiator_name' => 'Spec403 Browser Operator',
|
||||||
|
'context' => [
|
||||||
|
'raw_payload' => 'raw payload should stay hidden',
|
||||||
|
'provider_response' => 'provider response should stay hidden',
|
||||||
|
'stack_trace' => 'stack trace should stay hidden',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
EvidenceSnapshot::query()->create([
|
||||||
|
'managed_environment_id' => (int) $environment->getKey(),
|
||||||
|
'workspace_id' => (int) $environment->workspace_id,
|
||||||
|
'operation_run_id' => (int) $run->getKey(),
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'summary' => [
|
||||||
|
'dimension_count' => 2,
|
||||||
|
'missing_dimensions' => 0,
|
||||||
|
'stale_dimensions' => 0,
|
||||||
|
'raw_payload' => 'raw payload should stay hidden',
|
||||||
|
'provider_response' => 'provider response should stay hidden',
|
||||||
|
],
|
||||||
|
'generated_at' => now()->subMinutes(8),
|
||||||
|
'expires_at' => now()->addDay(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
EvidenceSnapshot::query()->create([
|
||||||
|
'managed_environment_id' => (int) $expiredEnvironment->getKey(),
|
||||||
|
'workspace_id' => (int) $expiredEnvironment->workspace_id,
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'summary' => [
|
||||||
|
'dimension_count' => 2,
|
||||||
|
'missing_dimensions' => 0,
|
||||||
|
'stale_dimensions' => 0,
|
||||||
|
],
|
||||||
|
'generated_at' => now()->subDays(2),
|
||||||
|
'expires_at' => now()->subMinute(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
EvidenceSnapshot::query()->create([
|
||||||
|
'managed_environment_id' => (int) $staleDimensionEnvironment->getKey(),
|
||||||
|
'workspace_id' => (int) $staleDimensionEnvironment->workspace_id,
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'summary' => [
|
||||||
|
'dimension_count' => 2,
|
||||||
|
'missing_dimensions' => 0,
|
||||||
|
'stale_dimensions' => 1,
|
||||||
|
],
|
||||||
|
'generated_at' => now()->subMinutes(6),
|
||||||
|
'expires_at' => now()->addDay(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
EvidenceSnapshot::query()->create([
|
||||||
|
'managed_environment_id' => (int) $missingDimensionEnvironment->getKey(),
|
||||||
|
'workspace_id' => (int) $missingDimensionEnvironment->workspace_id,
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'summary' => [
|
||||||
|
'dimension_count' => 2,
|
||||||
|
'missing_dimensions' => 1,
|
||||||
|
'stale_dimensions' => 0,
|
||||||
|
],
|
||||||
|
'generated_at' => now()->subMinutes(5),
|
||||||
|
'expires_at' => now()->addDay(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
spec403AuthenticateBrowser($this, $user, $environment);
|
||||||
|
|
||||||
|
$page = visit(route('admin.evidence.overview', [
|
||||||
|
'environment_id' => (int) $environment->getKey(),
|
||||||
|
]))
|
||||||
|
->resize(1366, 900)
|
||||||
|
->waitForText('Evidence proof workbench')
|
||||||
|
->assertSee('Spec403 Failed Proof Environment')
|
||||||
|
->assertSee('Operation proof')
|
||||||
|
->assertSee('Failed')
|
||||||
|
->assertSee('Spec403 Browser Operator')
|
||||||
|
->assertSee('Diagnostics')
|
||||||
|
->assertDontSee('Diagnostics - Collapsed')
|
||||||
|
->assertDontSee('Operation #')
|
||||||
|
->assertDontSee(OperationRunLinks::tenantlessView($run), false)
|
||||||
|
->assertDontSee('raw payload should stay hidden')
|
||||||
|
->assertDontSee('provider response should stay hidden')
|
||||||
|
->assertDontSee('stack trace should stay hidden')
|
||||||
|
->assertScript('(() => {
|
||||||
|
const item = document.querySelector("[data-proof-label=\"Operation proof\"]");
|
||||||
|
const badge = item?.querySelector("[data-testid=\"evidence-path-state-badge\"]");
|
||||||
|
|
||||||
|
return badge?.textContent.trim() === "Failed";
|
||||||
|
})()', true)
|
||||||
|
->assertScript('document.querySelector("[data-proof-label=\"Operation proof\"] a") === null', true)
|
||||||
|
->assertScript('(() => {
|
||||||
|
const legacyStates = ["Available", "Unavailable", "Not generated", "Not applicable", "Proof incomplete", "Empty", "Collapsed", "Unknown"];
|
||||||
|
const badges = Array.from(document.querySelectorAll("[data-testid=\"evidence-path-state-badge\"]"));
|
||||||
|
|
||||||
|
return badges.length > 0 && badges.every((badge) => !legacyStates.includes(badge.textContent.trim()));
|
||||||
|
})()', true)
|
||||||
|
->assertScript('document.querySelector("[data-testid=\"evidence-disclosure-diagnostics\"]")?.open === false', true)
|
||||||
|
->assertNoJavaScriptErrors()
|
||||||
|
->assertNoConsoleLogs();
|
||||||
|
|
||||||
|
$page->screenshot(true, 'spec403-evidence-currentness-operation-proof-failed');
|
||||||
|
spec403CopyBrowserScreenshot('spec403-evidence-currentness-operation-proof-failed');
|
||||||
|
|
||||||
|
visit(route('admin.evidence.overview', [
|
||||||
|
'environment_id' => (int) $expiredEnvironment->getKey(),
|
||||||
|
]))
|
||||||
|
->waitForText('Evidence snapshot required')
|
||||||
|
->assertSee('Spec403 Expired Evidence Environment')
|
||||||
|
->assertSee('Not configured')
|
||||||
|
->assertDontSee('View internal evidence details')
|
||||||
|
->assertScript('(() => {
|
||||||
|
const legacyStates = ["Available", "Unavailable", "Not generated", "Not applicable", "Proof incomplete", "Empty", "Collapsed", "Unknown"];
|
||||||
|
const badges = Array.from(document.querySelectorAll("[data-testid=\"evidence-path-state-badge\"]"));
|
||||||
|
|
||||||
|
return badges.length > 0 && badges.every((badge) => !legacyStates.includes(badge.textContent.trim()));
|
||||||
|
})()', true)
|
||||||
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepState === "Not configured"', true)
|
||||||
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
||||||
|
->assertNoJavaScriptErrors()
|
||||||
|
->assertNoConsoleLogs();
|
||||||
|
|
||||||
|
visit(route('admin.evidence.overview', [
|
||||||
|
'environment_id' => (int) $staleDimensionEnvironment->getKey(),
|
||||||
|
]))
|
||||||
|
->waitForText('Evidence snapshot required')
|
||||||
|
->assertSee('Spec403 Stale Dimension Environment')
|
||||||
|
->assertSee('Not configured')
|
||||||
|
->assertDontSee('View internal evidence details')
|
||||||
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepState === "Not configured"', true)
|
||||||
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
||||||
|
->assertNoJavaScriptErrors()
|
||||||
|
->assertNoConsoleLogs();
|
||||||
|
|
||||||
|
visit(route('admin.evidence.overview', [
|
||||||
|
'environment_id' => (int) $missingDimensionEnvironment->getKey(),
|
||||||
|
]))
|
||||||
|
->waitForText('Evidence snapshot required')
|
||||||
|
->assertSee('Spec403 Missing Dimension Environment')
|
||||||
|
->assertSee('Not configured')
|
||||||
|
->assertSee('Needs attention')
|
||||||
|
->assertSee('Refresh evidence before using this snapshot')
|
||||||
|
->assertDontSee('Partially complete')
|
||||||
|
->assertDontSee('Trustworthy artifact')
|
||||||
|
->assertDontSee('View internal evidence details')
|
||||||
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepState === "Not configured"', true)
|
||||||
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
||||||
|
->assertNoJavaScriptErrors()
|
||||||
|
->assertNoConsoleLogs();
|
||||||
|
|
||||||
|
visit(route('admin.evidence.overview', [
|
||||||
|
'environment_id' => (int) $foreignEnvironment->getKey(),
|
||||||
|
]))
|
||||||
|
->assertSee('404')
|
||||||
|
->assertDontSee('Spec403 Foreign Evidence Environment')
|
||||||
|
->assertNoJavaScriptErrors()
|
||||||
|
->assertNoConsoleLogs();
|
||||||
|
});
|
||||||
|
|
||||||
|
function spec403BrowserEnvironmentFor(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function spec403AuthenticateBrowser(mixed $test, User $user, ManagedEnvironment $environment): void
|
||||||
|
{
|
||||||
|
$workspaceId = (int) $environment->workspace_id;
|
||||||
|
$session = [
|
||||||
|
WorkspaceContext::SESSION_KEY => $workspaceId,
|
||||||
|
WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [
|
||||||
|
(string) $workspaceId => (int) $environment->getKey(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$test->actingAs($user)->withSession($session);
|
||||||
|
|
||||||
|
foreach ($session as $key => $value) {
|
||||||
|
session()->put($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAdminPanelContext($environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
function spec403CopyBrowserScreenshot(string $name): void
|
||||||
|
{
|
||||||
|
$filename = $name.'.png';
|
||||||
|
$source = base_path('tests/Browser/Screenshots/'.$filename);
|
||||||
|
$targetDirectory = repo_path('specs/403-evidence-anchor-currentness-runtime-closure/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.$filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -280,7 +280,7 @@ private static function evidenceSnapshot(ManagedEnvironment $environment): Evide
|
|||||||
'workspace_id' => (int) $environment->workspace_id,
|
'workspace_id' => (int) $environment->workspace_id,
|
||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
|
'summary' => ['dimension_count' => 5, 'missing_dimensions' => 0, 'stale_dimensions' => 0],
|
||||||
'generated_at' => now(),
|
'generated_at' => now(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'completeness_state' => $state,
|
'completeness_state' => $state,
|
||||||
'summary' => [
|
'summary' => [
|
||||||
|
'dimension_count' => $state === EvidenceCompletenessState::Complete->value ? 5 : 0,
|
||||||
'missing_dimensions' => $state === EvidenceCompletenessState::Missing->value ? 2 : 0,
|
'missing_dimensions' => $state === EvidenceCompletenessState::Missing->value ? 2 : 0,
|
||||||
'stale_dimensions' => 0,
|
'stale_dimensions' => 0,
|
||||||
],
|
],
|
||||||
@ -83,7 +84,7 @@
|
|||||||
'workspace_id' => (int) $tenant->workspace_id,
|
'workspace_id' => (int) $tenant->workspace_id,
|
||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'completeness_state' => $state,
|
'completeness_state' => $state,
|
||||||
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
|
'summary' => ['dimension_count' => 5, 'missing_dimensions' => 0, 'stale_dimensions' => 0],
|
||||||
'generated_at' => now(),
|
'generated_at' => now(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -130,6 +131,73 @@
|
|||||||
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $freshSnapshot], tenant: $freshTenant, panel: 'admin'), false);
|
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $freshSnapshot], tenant: $freshTenant, panel: 'admin'), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not present complete snapshots with missing dimensions as trustworthy current evidence on the overview', function (): void {
|
||||||
|
$environment = ManagedEnvironment::factory()->create([
|
||||||
|
'name' => 'Missing Dimensions ManagedEnvironment',
|
||||||
|
]);
|
||||||
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner');
|
||||||
|
|
||||||
|
$snapshot = $this->makeArtifactTruthEvidenceSnapshot($environment, [], [
|
||||||
|
'dimension_count' => 5,
|
||||||
|
'missing_dimensions' => 1,
|
||||||
|
'stale_dimensions' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Filament::setTenant(null, true);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id])
|
||||||
|
->get(route('admin.evidence.overview'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Missing Dimensions ManagedEnvironment')
|
||||||
|
->assertSee('Needs attention')
|
||||||
|
->assertSee('Refresh evidence before using this snapshot')
|
||||||
|
->assertDontSee('Partially complete')
|
||||||
|
->assertDontSee('Trustworthy artifact')
|
||||||
|
->assertDontSee('Create a current review from this evidence snapshot')
|
||||||
|
->assertDontSee(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $environment, panel: 'admin'), false);
|
||||||
|
|
||||||
|
$this->get(route('admin.evidence.overview', ['environment_id' => (int) $environment->getKey()]))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Evidence snapshot required')
|
||||||
|
->assertSee('Not configured')
|
||||||
|
->assertDontSee('View internal evidence details')
|
||||||
|
->assertDontSee(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $environment, panel: 'admin'), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not present complete snapshots without captured dimensions as current evidence on the overview', function (): void {
|
||||||
|
$environment = ManagedEnvironment::factory()->create([
|
||||||
|
'name' => 'Empty Dimensions ManagedEnvironment',
|
||||||
|
]);
|
||||||
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner');
|
||||||
|
|
||||||
|
$snapshot = $this->makeArtifactTruthEvidenceSnapshot($environment, [], [
|
||||||
|
'dimension_count' => 0,
|
||||||
|
'missing_dimensions' => 0,
|
||||||
|
'stale_dimensions' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Filament::setTenant(null, true);
|
||||||
|
|
||||||
|
$this->actingAs($user)
|
||||||
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id])
|
||||||
|
->get(route('admin.evidence.overview'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Empty Dimensions ManagedEnvironment')
|
||||||
|
->assertSee('Needs attention')
|
||||||
|
->assertSee('A proof record exists, but no usable captured evidence is available yet.')
|
||||||
|
->assertDontSee('Trustworthy artifact')
|
||||||
|
->assertDontSee('Create a current review from this evidence snapshot')
|
||||||
|
->assertDontSee(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $environment, panel: 'admin'), false);
|
||||||
|
|
||||||
|
$this->get(route('admin.evidence.overview', ['environment_id' => (int) $environment->getKey()]))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Evidence snapshot required')
|
||||||
|
->assertSee('Not configured')
|
||||||
|
->assertDontSee('View internal evidence details')
|
||||||
|
->assertDontSee(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $environment, panel: 'admin'), false);
|
||||||
|
});
|
||||||
|
|
||||||
it('seeds the native entitled-tenant prefilter once and clears it through the page action', function (): void {
|
it('seeds the native entitled-tenant prefilter once and clears it through the page action', function (): void {
|
||||||
$tenantA = ManagedEnvironment::factory()->create();
|
$tenantA = ManagedEnvironment::factory()->create();
|
||||||
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner');
|
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner');
|
||||||
@ -144,7 +212,7 @@
|
|||||||
'workspace_id' => (int) $tenantA->workspace_id,
|
'workspace_id' => (int) $tenantA->workspace_id,
|
||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
|
'summary' => ['dimension_count' => 5, 'missing_dimensions' => 0, 'stale_dimensions' => 0],
|
||||||
'generated_at' => now(),
|
'generated_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -153,7 +221,7 @@
|
|||||||
'workspace_id' => (int) $tenantB->workspace_id,
|
'workspace_id' => (int) $tenantB->workspace_id,
|
||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
|
'summary' => ['dimension_count' => 5, 'missing_dimensions' => 0, 'stale_dimensions' => 0],
|
||||||
'generated_at' => now(),
|
'generated_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
use App\Support\EnvironmentReviewStatus;
|
use App\Support\EnvironmentReviewStatus;
|
||||||
use App\Support\Evidence\EvidenceCompletenessState;
|
use App\Support\Evidence\EvidenceCompletenessState;
|
||||||
use App\Support\Evidence\EvidenceSnapshotStatus;
|
use App\Support\Evidence\EvidenceSnapshotStatus;
|
||||||
|
use App\Support\OperationRunLinks;
|
||||||
use App\Support\OperationRunOutcome;
|
use App\Support\OperationRunOutcome;
|
||||||
use App\Support\OperationRunStatus;
|
use App\Support\OperationRunStatus;
|
||||||
use App\Support\OperationRunType;
|
use App\Support\OperationRunType;
|
||||||
@ -31,19 +32,20 @@
|
|||||||
->assertSee('Generate evidence snapshot')
|
->assertSee('Generate evidence snapshot')
|
||||||
->assertSee('Evidence readiness flow')
|
->assertSee('Evidence readiness flow')
|
||||||
->assertSee('Evidence proof')
|
->assertSee('Evidence proof')
|
||||||
->assertSee('Diagnostics - Collapsed')
|
->assertSee('Diagnostics')
|
||||||
|
->assertDontSee('Diagnostics - Collapsed')
|
||||||
->assertDontSee('raw payload should stay hidden')
|
->assertDontSee('raw payload should stay hidden')
|
||||||
->assertDontSee('provider response should stay hidden')
|
->assertDontSee('provider response should stay hidden')
|
||||||
->assertDontSee('stack trace should stay hidden');
|
->assertDontSee('stack trace should stay hidden');
|
||||||
|
|
||||||
$content = $component->html();
|
$content = $component->html();
|
||||||
|
|
||||||
spec337AssertFlowStep($content, 'Source data selected', 'Available', false);
|
spec337AssertFlowStep($content, 'Source data selected', 'Ready', false);
|
||||||
spec337AssertFlowStep($content, 'Evidence snapshot', 'Missing', true);
|
spec337AssertFlowStep($content, 'Evidence snapshot', 'Not configured', true);
|
||||||
spec337AssertFlowStep($content, 'Stored report', 'Unavailable', false);
|
spec337AssertFlowStep($content, 'Stored report', 'Blocked', false);
|
||||||
spec337AssertFlowStep($content, 'Review pack', 'Unavailable', false);
|
spec337AssertFlowStep($content, 'Review pack', 'Blocked', false);
|
||||||
spec337AssertFlowStep($content, 'Customer-safe output', 'Not ready', false);
|
spec337AssertFlowStep($content, 'Customer-safe output', 'Not configured', false);
|
||||||
spec337AssertFlowStep($content, 'Export / delivery', 'Unavailable', false);
|
spec337AssertFlowStep($content, 'Export / delivery', 'Not configured', false);
|
||||||
|
|
||||||
expect(substr_count($content, 'data-testid="evidence-readiness-step"'))->toBe(6)
|
expect(substr_count($content, 'data-testid="evidence-readiness-step"'))->toBe(6)
|
||||||
->and($content)->toContain('data-testid="evidence-readiness-flow" class="@container')
|
->and($content)->toContain('data-testid="evidence-readiness-flow" class="@container')
|
||||||
@ -65,11 +67,11 @@
|
|||||||
|
|
||||||
$content = $component->html();
|
$content = $component->html();
|
||||||
|
|
||||||
spec337AssertFlowStep($content, 'Evidence snapshot', 'Available', false);
|
spec337AssertFlowStep($content, 'Evidence snapshot', 'Ready', false);
|
||||||
spec337AssertFlowStep($content, 'Stored report', 'Missing', true);
|
spec337AssertFlowStep($content, 'Stored report', 'Not configured', true);
|
||||||
spec337AssertFlowStep($content, 'Review pack', 'Unavailable', false);
|
spec337AssertFlowStep($content, 'Review pack', 'Blocked', false);
|
||||||
spec337AssertFlowStep($content, 'Customer-safe output', 'Not ready', false);
|
spec337AssertFlowStep($content, 'Customer-safe output', 'Not configured', false);
|
||||||
spec337AssertFlowStep($content, 'Export / delivery', 'Unavailable', false);
|
spec337AssertFlowStep($content, 'Export / delivery', 'Not configured', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders review pack required with capability-aware primary action', function (): void {
|
it('renders review pack required with capability-aware primary action', function (): void {
|
||||||
@ -86,7 +88,7 @@
|
|||||||
->assertDontSee('Customer-safe output ready')
|
->assertDontSee('Customer-safe output ready')
|
||||||
->assertDontSee('Review pack export available');
|
->assertDontSee('Review pack export available');
|
||||||
|
|
||||||
spec337AssertFlowStep($ownerComponent->html(), 'Review pack', 'Required', true);
|
spec337AssertFlowStep($ownerComponent->html(), 'Review pack', 'Not configured', true);
|
||||||
|
|
||||||
spec337EvidenceOverviewLivewire($readonly, $environment)
|
spec337EvidenceOverviewLivewire($readonly, $environment)
|
||||||
->assertSee('Review pack required')
|
->assertSee('Review pack required')
|
||||||
@ -118,11 +120,11 @@
|
|||||||
|
|
||||||
$content = $component->html();
|
$content = $component->html();
|
||||||
|
|
||||||
spec337AssertFlowStep($content, 'Evidence snapshot', 'Available', false);
|
spec337AssertFlowStep($content, 'Evidence snapshot', 'Ready', false);
|
||||||
spec337AssertFlowStep($content, 'Stored report', 'Available', false);
|
spec337AssertFlowStep($content, 'Stored report', 'Ready', false);
|
||||||
spec337AssertFlowStep($content, 'Review pack', 'Available', false);
|
spec337AssertFlowStep($content, 'Review pack', 'Ready', false);
|
||||||
spec337AssertFlowStep($content, 'Customer-safe output', 'Ready', false);
|
spec337AssertFlowStep($content, 'Customer-safe output', 'Ready', false);
|
||||||
spec337AssertFlowStep($content, 'Export / delivery', 'Available', true);
|
spec337AssertFlowStep($content, 'Export / delivery', 'Ready', true);
|
||||||
|
|
||||||
expect($review->current_export_review_pack_id)->toBe($pack->getKey());
|
expect($review->current_export_review_pack_id)->toBe($pack->getKey());
|
||||||
});
|
});
|
||||||
@ -145,11 +147,10 @@
|
|||||||
$generatingComponent = spec337EvidenceOverviewLivewire($user, $generatingEnvironment)
|
$generatingComponent = spec337EvidenceOverviewLivewire($user, $generatingEnvironment)
|
||||||
->assertSee('Evidence snapshot required')
|
->assertSee('Evidence snapshot required')
|
||||||
->assertDontSee('View operation progress')
|
->assertDontSee('View operation progress')
|
||||||
->assertDontSee('Generating')
|
|
||||||
->assertDontSee('Spec337 Operator')
|
->assertDontSee('Spec337 Operator')
|
||||||
->assertDontSee('Customer-safe output ready');
|
->assertDontSee('Customer-safe output ready');
|
||||||
|
|
||||||
spec337AssertFlowStep($generatingComponent->html(), 'Evidence snapshot', 'Missing', true);
|
spec337AssertFlowStep($generatingComponent->html(), 'Evidence snapshot', 'Not configured', true);
|
||||||
|
|
||||||
$failedEnvironment = createUserWithTenant(user: $user, role: 'owner', workspaceRole: 'manager')[1];
|
$failedEnvironment = createUserWithTenant(user: $user, role: 'owner', workspaceRole: 'manager')[1];
|
||||||
$failedRun = OperationRun::factory()->forTenant($failedEnvironment)->create([
|
$failedRun = OperationRun::factory()->forTenant($failedEnvironment)->create([
|
||||||
@ -171,10 +172,11 @@
|
|||||||
|
|
||||||
$failedComponent = spec337EvidenceOverviewLivewire($user, $failedEnvironment)
|
$failedComponent = spec337EvidenceOverviewLivewire($user, $failedEnvironment)
|
||||||
->assertSee('Review pack generation failed')
|
->assertSee('Review pack generation failed')
|
||||||
->assertSee('Review operation')
|
->assertSee('Open review pack')
|
||||||
->assertSee('Operation proof')
|
->assertSee('Operation proof')
|
||||||
->assertSee('Failed')
|
->assertSee('Failed')
|
||||||
->assertSee('Spec337 Reviewer')
|
->assertSee('Spec337 Reviewer')
|
||||||
|
->assertDontSee(OperationRunLinks::tenantlessView($failedRun), false)
|
||||||
->assertDontSee('Review pack export available');
|
->assertDontSee('Review pack export available');
|
||||||
|
|
||||||
spec337AssertFlowStep($failedComponent->html(), 'Review pack', 'Failed', true);
|
spec337AssertFlowStep($failedComponent->html(), 'Review pack', 'Failed', true);
|
||||||
|
|||||||
@ -77,7 +77,6 @@
|
|||||||
|
|
||||||
$component
|
$component
|
||||||
->assertSee('What is the current review pack output state?')
|
->assertSee('What is the current review pack output state?')
|
||||||
->assertSee('Customer-safe review pack ready')
|
|
||||||
->assertSee('Stakeholders can use the current review pack and released review as the evidence path.')
|
->assertSee('Stakeholders can use the current review pack and released review as the evidence path.')
|
||||||
->assertSee('Review consumption flow')
|
->assertSee('Review consumption flow')
|
||||||
->assertSee('Review data')
|
->assertSee('Review data')
|
||||||
@ -87,17 +86,19 @@
|
|||||||
->assertSee('Findings needing attention')
|
->assertSee('Findings needing attention')
|
||||||
->assertSee('No open findings require customer action.')
|
->assertSee('No open findings require customer action.')
|
||||||
->assertSee('Review pack state')
|
->assertSee('Review pack state')
|
||||||
->assertSee('Export ready')
|
->assertSee('Ready')
|
||||||
->assertSee('Download customer-safe review pack')
|
->assertSee('Download customer-safe review pack')
|
||||||
->assertDontSee('Operation proof')
|
->assertDontSee('Operation proof')
|
||||||
->assertDontSee('Spec342 Operator')
|
->assertDontSee('Spec342 Operator')
|
||||||
|
->assertDontSee('Customer-safe review pack ready')
|
||||||
->assertDontSee('Auditor-ready')
|
->assertDontSee('Auditor-ready')
|
||||||
->assertDontSee('environment is healthy')
|
->assertDontSee('environment is healthy')
|
||||||
->assertDontSee('compliant');
|
->assertDontSee('compliant');
|
||||||
|
|
||||||
$html = $component->html();
|
$html = $component->html();
|
||||||
|
|
||||||
expect(substr_count($html, 'data-testid="customer-review-primary-action"'))->toBe(1)
|
expect($html)->toMatch('/<h2[^>]*>\s*Ready\s*<\/h2>/')
|
||||||
|
->and(substr_count($html, 'data-testid="customer-review-primary-action"'))->toBe(1)
|
||||||
->and(substr_count($html, 'data-testid="customer-review-readiness-step"'))->toBe(6)
|
->and(substr_count($html, 'data-testid="customer-review-readiness-step"'))->toBe(6)
|
||||||
->and($html)->toContain('source_surface=customer_review_workspace')
|
->and($html)->toContain('source_surface=customer_review_workspace')
|
||||||
->and($html)->not->toContain('/admin/t/');
|
->and($html)->not->toContain('/admin/t/');
|
||||||
@ -149,21 +150,22 @@
|
|||||||
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
||||||
|
|
||||||
$component = spec342WorkspaceComponent($user, $environment)
|
$component = spec342WorkspaceComponent($user, $environment)
|
||||||
->assertSee('Output not customer-ready')
|
|
||||||
->assertSee('Review blockers are still recorded for this output.')
|
->assertSee('Review blockers are still recorded for this output.')
|
||||||
->assertSee('Customer-safe output')
|
->assertSee('Customer-safe output')
|
||||||
->assertSee('Needs review')
|
->assertSee('Needs attention')
|
||||||
->assertSee('Diagnostics')
|
->assertSee('Diagnostics')
|
||||||
->assertSee('Collapsed')
|
->assertSee('Not configured')
|
||||||
->assertDontSee('Ready to share')
|
->assertDontSee('Ready to share')
|
||||||
->assertSee('Available')
|
->assertSee('Ready')
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
|
->assertDontSee('Output not customer-ready')
|
||||||
->assertDontSee('raw payload should stay hidden')
|
->assertDontSee('raw payload should stay hidden')
|
||||||
->assertDontSee('provider response should stay hidden')
|
->assertDontSee('provider response should stay hidden')
|
||||||
->assertDontSee('stack trace should stay hidden')
|
->assertDontSee('stack trace should stay hidden')
|
||||||
->assertDontSee('spec342-hidden-fingerprint');
|
->assertDontSee('spec342-hidden-fingerprint');
|
||||||
|
|
||||||
expect($component->html())->not->toContain('data-testid="customer-review-diagnostics" open');
|
expect($component->html())->toMatch('/<h2[^>]*>\s*Needs attention\s*<\/h2>/')
|
||||||
|
->not->toContain('data-testid="customer-review-diagnostics" open');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses concrete findings follow-up copy and keeps review-pack download primary-only', function (): void {
|
it('uses concrete findings follow-up copy and keeps review-pack download primary-only', function (): void {
|
||||||
@ -202,7 +204,7 @@
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$component = spec342WorkspaceComponent($user, $environment)
|
$component = spec342WorkspaceComponent($user, $environment)
|
||||||
->assertSee('Published with limitations')
|
->assertSee('Needs attention')
|
||||||
->assertSee('1 open finding needs attention; 1 is high impact. Keep open findings visible before customer handoff.')
|
->assertSee('1 open finding needs attention; 1 is high impact. Keep open findings visible before customer handoff.')
|
||||||
->assertSee('Do not treat this review as share-ready until open findings are resolved, accepted, or explicitly reviewed.')
|
->assertSee('Do not treat this review as share-ready until open findings are resolved, accepted, or explicitly reviewed.')
|
||||||
->assertSee('Open review')
|
->assertSee('Open review')
|
||||||
|
|||||||
@ -38,13 +38,15 @@
|
|||||||
]);
|
]);
|
||||||
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
||||||
|
|
||||||
spec347WorkspaceComponent($user, $environment)
|
$component = spec347WorkspaceComponent($user, $environment)
|
||||||
->assertSee('What is the current review pack output state?')
|
->assertSee('What is the current review pack output state?')
|
||||||
->assertSee('Customer-safe review pack ready')
|
|
||||||
->assertSee('Customer-safe')
|
->assertSee('Customer-safe')
|
||||||
->assertSee('Download customer-safe review pack')
|
->assertSee('Download customer-safe review pack')
|
||||||
->assertSee('PII excluded')
|
->assertSee('PII excluded')
|
||||||
|
->assertDontSee('Customer-safe review pack ready')
|
||||||
->assertDontSee('Ready to share');
|
->assertDontSee('Ready to share');
|
||||||
|
|
||||||
|
expect($component->html())->toMatch('/<h2[^>]*>\s*Ready\s*<\/h2>/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows blocked output guidance when evidence is incomplete and review blockers remain recorded', function (): void {
|
it('shows blocked output guidance when evidence is incomplete and review blockers remain recorded', function (): void {
|
||||||
@ -71,12 +73,15 @@
|
|||||||
]);
|
]);
|
||||||
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
||||||
|
|
||||||
spec347WorkspaceComponent($user, $environment)
|
$component = spec347WorkspaceComponent($user, $environment)
|
||||||
->assertSee('Output not customer-ready')
|
|
||||||
->assertSee('Review blockers are still recorded for this output.')
|
->assertSee('Review blockers are still recorded for this output.')
|
||||||
->assertSee('Requires review')
|
->assertSee('Needs attention')
|
||||||
|
->assertDontSee('Output not customer-ready')
|
||||||
|
->assertDontSee('Requires review')
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
->assertDontSee('Ready to share');
|
->assertDontSee('Ready to share');
|
||||||
|
|
||||||
|
expect($component->html())->toMatch('/<h2[^>]*>\s*Needs attention\s*<\/h2>/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the internal-only workspace state when the export contains pii', function (): void {
|
it('shows the internal-only workspace state when the export contains pii', function (): void {
|
||||||
@ -104,14 +109,17 @@
|
|||||||
]);
|
]);
|
||||||
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
||||||
|
|
||||||
spec347WorkspaceComponent($user, $environment)
|
$component = spec347WorkspaceComponent($user, $environment)
|
||||||
->assertSee('Internal review package available')
|
->assertSee('Needs attention')
|
||||||
->assertSee('Internal only')
|
|
||||||
->assertSee('Contains PII')
|
->assertSee('Contains PII')
|
||||||
->assertSee('Review PII/redaction state')
|
->assertSee('Review PII/redaction state')
|
||||||
->assertDontSee('Download internal review pack')
|
->assertDontSee('Download internal review pack')
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
|
->assertDontSee('Internal only')
|
||||||
|
->assertDontSee('Internal review package available')
|
||||||
->assertDontSee('Customer-safe review pack ready');
|
->assertDontSee('Customer-safe review pack ready');
|
||||||
|
|
||||||
|
expect($component->html())->toMatch('/<h2[^>]*>\s*Needs attention\s*<\/h2>/');
|
||||||
});
|
});
|
||||||
|
|
||||||
function spec347WorkspaceComponent(User $user, ManagedEnvironment $environment): mixed
|
function spec347WorkspaceComponent(User $user, ManagedEnvironment $environment): mixed
|
||||||
|
|||||||
@ -54,18 +54,20 @@
|
|||||||
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
||||||
|
|
||||||
$component = spec349WorkspaceComponent($user, $environment)
|
$component = spec349WorkspaceComponent($user, $environment)
|
||||||
->assertSee('Output not customer-ready')
|
->assertSee('Needs attention')
|
||||||
->assertSee('Create next review')
|
->assertSee('Create next review')
|
||||||
->assertSee('Inspect review blockers')
|
->assertSee('Inspect review blockers')
|
||||||
->assertSee('Evidence basis incomplete')
|
->assertSee('Evidence basis incomplete')
|
||||||
->assertSee('Required review sections missing')
|
->assertSee('Required review sections missing')
|
||||||
->assertSee('Internal package includes PII')
|
->assertSee('Internal package includes PII')
|
||||||
->assertSee('Create the next review cycle from the latest eligible evidence basis.')
|
->assertSee('Create the next review cycle from the latest eligible evidence basis.')
|
||||||
|
->assertDontSee('Output not customer-ready')
|
||||||
->assertDontSee('Technical details');
|
->assertDontSee('Technical details');
|
||||||
|
|
||||||
$html = $component->html();
|
$html = $component->html();
|
||||||
|
|
||||||
expect(substr_count($html, 'data-testid="customer-review-primary-action"'))->toBe(1)
|
expect($html)->toMatch('/<h2[^>]*>\s*Needs attention\s*<\/h2>/')
|
||||||
|
->and(substr_count($html, 'data-testid="customer-review-primary-action"'))->toBe(1)
|
||||||
->and($html)->toContain('data-testid="customer-review-output-limitations"')
|
->and($html)->toContain('data-testid="customer-review-output-limitations"')
|
||||||
->and($html)->not->toContain('data-testid="customer-review-output-limitations" open')
|
->and($html)->not->toContain('data-testid="customer-review-output-limitations" open')
|
||||||
->and($html)->toContain('data-testid="customer-review-action-help"')
|
->and($html)->toContain('data-testid="customer-review-action-help"')
|
||||||
@ -111,10 +113,11 @@
|
|||||||
->test(CustomerReviewWorkspace::class)
|
->test(CustomerReviewWorkspace::class)
|
||||||
->assertSee('Environment filter:')
|
->assertSee('Environment filter:')
|
||||||
->assertSee('Spec349 Internal')
|
->assertSee('Spec349 Internal')
|
||||||
->assertSee('Internal review package available')
|
->assertSee('Needs attention')
|
||||||
->assertDontSee('Download internal review pack')
|
->assertDontSee('Download internal review pack')
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
->assertDontSee('Download governance package')
|
->assertDontSee('Download governance package')
|
||||||
|
->assertDontSee('Internal review package available')
|
||||||
->assertDontSee('Ready to share');
|
->assertDontSee('Ready to share');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -56,10 +56,13 @@
|
|||||||
|
|
||||||
$component = spec350WorkspaceComponent($user, $environment)
|
$component = spec350WorkspaceComponent($user, $environment)
|
||||||
->assertSee('What is the current review pack output state?')
|
->assertSee('What is the current review pack output state?')
|
||||||
->assertSee('Output not customer-ready')
|
->assertSee('Needs attention')
|
||||||
->assertSee('Create next review')
|
->assertSee('Create next review')
|
||||||
->assertSee('Review blockers are still recorded for this output.')
|
->assertSee('Review blockers are still recorded for this output.')
|
||||||
->assertSee('Inspect review blockers');
|
->assertSee('Inspect review blockers')
|
||||||
|
->assertDontSee('Output not customer-ready');
|
||||||
|
|
||||||
|
expect($component->html())->toMatch('/<h2[^>]*>\s*Needs attention\s*<\/h2>/');
|
||||||
|
|
||||||
$payload = $component->instance()->latestReviewConsumptionPayload();
|
$payload = $component->instance()->latestReviewConsumptionPayload();
|
||||||
|
|
||||||
@ -111,9 +114,12 @@
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$component = spec350WorkspaceComponent($user, $environment)
|
$component = spec350WorkspaceComponent($user, $environment)
|
||||||
->assertSee('Published with limitations')
|
->assertSee('Needs attention')
|
||||||
->assertSee('Keep open findings visible before customer handoff.')
|
->assertSee('Keep open findings visible before customer handoff.')
|
||||||
->assertSee('Open review');
|
->assertSee('Open review')
|
||||||
|
->assertDontSee('Published with limitations');
|
||||||
|
|
||||||
|
expect($component->html())->toMatch('/<h2[^>]*>\s*Needs attention\s*<\/h2>/');
|
||||||
|
|
||||||
$payload = $component->instance()->latestReviewConsumptionPayload();
|
$payload = $component->instance()->latestReviewConsumptionPayload();
|
||||||
|
|
||||||
|
|||||||
@ -46,14 +46,17 @@
|
|||||||
]);
|
]);
|
||||||
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
||||||
|
|
||||||
spec385WorkspaceComponent($user, $environment)
|
$component = spec385WorkspaceComponent($user, $environment)
|
||||||
->assertSee('Output not customer-ready')
|
->assertSee('Needs attention')
|
||||||
->assertSee('Baseline readiness blocked')
|
->assertSee('Baseline readiness blocked')
|
||||||
->assertSee('Open baseline resolution')
|
->assertSee('Open baseline resolution')
|
||||||
|
->assertDontSee('Output not customer-ready')
|
||||||
->assertDontSee('baseline_identity_unresolved')
|
->assertDontSee('baseline_identity_unresolved')
|
||||||
->assertDontSee('provider_resource_id')
|
->assertDontSee('provider_resource_id')
|
||||||
->assertDontSee('canonical_subject_key')
|
->assertDontSee('canonical_subject_key')
|
||||||
->assertDontSee('internal_diagnostics');
|
->assertDontSee('internal_diagnostics');
|
||||||
|
|
||||||
|
expect($component->html())->toMatch('/<h2[^>]*>\s*Needs attention\s*<\/h2>/');
|
||||||
});
|
});
|
||||||
|
|
||||||
function spec385WorkspaceComponent(User $user, ManagedEnvironment $environment): mixed
|
function spec385WorkspaceComponent(User $user, ManagedEnvironment $environment): mixed
|
||||||
|
|||||||
@ -88,7 +88,7 @@
|
|||||||
'workspace_id' => (int) $tenantA->workspace_id,
|
'workspace_id' => (int) $tenantA->workspace_id,
|
||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
|
'summary' => ['dimension_count' => 5, 'missing_dimensions' => 0, 'stale_dimensions' => 0],
|
||||||
'generated_at' => now(),
|
'generated_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -105,6 +105,7 @@
|
|||||||
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenantA->workspace_id])
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $tenantA->workspace_id])
|
||||||
->get(route('admin.evidence.overview'))
|
->get(route('admin.evidence.overview'))
|
||||||
->assertOk()
|
->assertOk()
|
||||||
->assertSee(EvidenceSnapshotResource::getUrl('view', ['record' => $allowedSnapshot], tenant: $tenantA, panel: 'admin'))
|
->assertSee($tenantA->name)
|
||||||
->assertDontSee(EvidenceSnapshotResource::getUrl('view', ['record' => $deniedSnapshot], tenant: $tenantDenied, panel: 'admin'));
|
->assertDontSee(EvidenceSnapshotResource::getUrl('view', ['record' => $allowedSnapshot], tenant: $tenantA, panel: 'admin'), false)
|
||||||
|
->assertDontSee(EvidenceSnapshotResource::getUrl('view', ['record' => $deniedSnapshot], tenant: $tenantDenied, panel: 'admin'), false);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -85,7 +85,11 @@ function spec314EvidenceSnapshot(ManagedEnvironment $environment, string $comple
|
|||||||
'workspace_id' => (int) $environment->workspace_id,
|
'workspace_id' => (int) $environment->workspace_id,
|
||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'completeness_state' => $completenessState,
|
'completeness_state' => $completenessState,
|
||||||
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
|
'summary' => [
|
||||||
|
'dimension_count' => $completenessState === EvidenceCompletenessState::Complete->value ? 5 : 0,
|
||||||
|
'missing_dimensions' => 0,
|
||||||
|
'stale_dimensions' => 0,
|
||||||
|
],
|
||||||
'generated_at' => now(),
|
'generated_at' => now(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,21 +51,20 @@
|
|||||||
->assertSee('Operation proof')
|
->assertSee('Operation proof')
|
||||||
->assertSee('Stored report / export')
|
->assertSee('Stored report / export')
|
||||||
->assertSee('Spec 329 Demo Tenant - Produktion')
|
->assertSee('Spec 329 Demo Tenant - Produktion')
|
||||||
->assertSee('Proof incomplete')
|
|
||||||
->assertSee('A proof record exists, but no usable captured evidence is available yet.')
|
|
||||||
->assertSee('Primary evidence snapshot is empty.')
|
|
||||||
->assertSee('Supporting proof exists through the review pack, stored report, and operation record.')
|
|
||||||
->assertSee('Empty')
|
|
||||||
->assertSee('Ready')
|
->assertSee('Ready')
|
||||||
->assertSee('Available')
|
->assertSee('Historical')
|
||||||
->assertSee('Search evidence or next step')
|
->assertSee('Search evidence or next step')
|
||||||
->assertSee('Diagnostics - Collapsed')
|
->assertSee('Diagnostics')
|
||||||
->assertSee('Evidence inventory')
|
->assertSee('Evidence inventory')
|
||||||
->assertSee('View internal evidence details')
|
->assertSee('View internal evidence details')
|
||||||
|
->assertDontSee('Proof incomplete')
|
||||||
|
->assertDontSee('Not generated')
|
||||||
|
->assertDontSee('Not applicable')
|
||||||
|
->assertDontSee('Diagnostics - Collapsed')
|
||||||
->assertDontSee('Open evidence snapshot')
|
->assertDontSee('Open evidence snapshot')
|
||||||
|
->assertDontSee(\App\Support\OperationRunLinks::tenantlessView($run), false)
|
||||||
->assertSee(ReviewPackResource::getUrl('view', ['record' => $reviewPack], tenant: $environment, panel: 'admin'), false)
|
->assertSee(ReviewPackResource::getUrl('view', ['record' => $reviewPack], tenant: $environment, panel: 'admin'), false)
|
||||||
->assertSee(StoredReportResource::getUrl('view', ['record' => $storedReport], tenant: $environment, panel: 'admin'), false)
|
->assertSee(StoredReportResource::getUrl('view', ['record' => $storedReport], tenant: $environment, panel: 'admin'), false)
|
||||||
->assertSee(\App\Support\OperationRunLinks::tenantlessView($run), false)
|
|
||||||
->assertDontSee('The artifact row exists, but it does not contain usable captured content.')
|
->assertDontSee('The artifact row exists, but it does not contain usable captured content.')
|
||||||
->assertDontSee('artifact row exists')
|
->assertDontSee('artifact row exists')
|
||||||
->assertDontSee('Search tenant or next')
|
->assertDontSee('Search tenant or next')
|
||||||
@ -203,6 +202,7 @@ function spec329EvidenceFixture(): array
|
|||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
'summary' => [
|
'summary' => [
|
||||||
|
'dimension_count' => 5,
|
||||||
'missing_dimensions' => 0,
|
'missing_dimensions' => 0,
|
||||||
'stale_dimensions' => 0,
|
'stale_dimensions' => 0,
|
||||||
'raw_payload' => 'raw payload should stay hidden',
|
'raw_payload' => 'raw payload should stay hidden',
|
||||||
|
|||||||
@ -0,0 +1,318 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Filament\Pages\Monitoring\EvidenceOverview;
|
||||||
|
use App\Models\EvidenceSnapshot;
|
||||||
|
use App\Models\ManagedEnvironment;
|
||||||
|
use App\Models\OperationRun;
|
||||||
|
use App\Models\ReviewPack;
|
||||||
|
use App\Services\Evidence\EvidenceAnchorResolver;
|
||||||
|
use App\Services\Evidence\EvidenceAnchorResult;
|
||||||
|
use App\Support\Evidence\EvidenceCompletenessState;
|
||||||
|
use App\Support\Evidence\EvidenceSnapshotStatus;
|
||||||
|
use App\Support\OperationRunOutcome;
|
||||||
|
use App\Support\OperationRunStatus;
|
||||||
|
use App\Support\ReviewPackStatus;
|
||||||
|
use App\Support\Workspaces\WorkspaceContext;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
it('Spec403 current evidence anchors require scoped active complete non-expired evidence', function (array $attributes): void {
|
||||||
|
$environment = ManagedEnvironment::factory()->create();
|
||||||
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner');
|
||||||
|
|
||||||
|
spec403EvidenceSnapshot($environment, $attributes);
|
||||||
|
|
||||||
|
setAdminPanelContext($environment);
|
||||||
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $environment->workspace_id);
|
||||||
|
|
||||||
|
$result = app(EvidenceAnchorResolver::class)->currentForScope(
|
||||||
|
$environment->workspace,
|
||||||
|
$environment,
|
||||||
|
$user,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($result->state)->toBe(EvidenceAnchorResult::STATE_NEEDS_ATTENTION)
|
||||||
|
->and($result->canLink)->toBeFalse()
|
||||||
|
->and($result->isCurrent)->toBeFalse()
|
||||||
|
->and($result->primaryReason)->toContain('No complete active evidence snapshot');
|
||||||
|
|
||||||
|
EvidenceSnapshot::query()
|
||||||
|
->where('workspace_id', (int) $environment->workspace_id)
|
||||||
|
->where('managed_environment_id', (int) $environment->getKey())
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
$currentSnapshot = spec403EvidenceSnapshot($environment, [
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'generated_at' => now(),
|
||||||
|
'expires_at' => now()->addDay(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$currentResult = app(EvidenceAnchorResolver::class)->currentForScope(
|
||||||
|
$environment->workspace,
|
||||||
|
$environment,
|
||||||
|
$user,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect($currentResult->state)->toBe(EvidenceAnchorResult::STATE_READY)
|
||||||
|
->and($currentResult->evidenceSnapshotId)->toBe((int) $currentSnapshot->getKey())
|
||||||
|
->and($currentResult->canLink)->toBeTrue()
|
||||||
|
->and($currentResult->isCurrent)->toBeTrue();
|
||||||
|
})->with([
|
||||||
|
'queued evidence' => [[
|
||||||
|
'status' => EvidenceSnapshotStatus::Queued->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
]],
|
||||||
|
'generating evidence' => [[
|
||||||
|
'status' => EvidenceSnapshotStatus::Generating->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
]],
|
||||||
|
'failed evidence' => [[
|
||||||
|
'status' => EvidenceSnapshotStatus::Failed->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
]],
|
||||||
|
'partial evidence' => [[
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Partial->value,
|
||||||
|
]],
|
||||||
|
'complete evidence with missing dimensions' => [[
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'summary' => [
|
||||||
|
'dimension_count' => 2,
|
||||||
|
'missing_dimensions' => 1,
|
||||||
|
'stale_dimensions' => 0,
|
||||||
|
],
|
||||||
|
]],
|
||||||
|
'complete evidence with no usable captured dimensions' => [[
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'summary' => [
|
||||||
|
'dimension_count' => 0,
|
||||||
|
'missing_dimensions' => 0,
|
||||||
|
'stale_dimensions' => 0,
|
||||||
|
],
|
||||||
|
]],
|
||||||
|
'complete evidence with stale dimensions' => [[
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'summary' => [
|
||||||
|
'dimension_count' => 2,
|
||||||
|
'missing_dimensions' => 0,
|
||||||
|
'stale_dimensions' => 1,
|
||||||
|
],
|
||||||
|
]],
|
||||||
|
'expired evidence' => [[
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'expires_at' => now()->subMinute(),
|
||||||
|
]],
|
||||||
|
'superseded evidence' => [[
|
||||||
|
'status' => EvidenceSnapshotStatus::Superseded->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
]],
|
||||||
|
]);
|
||||||
|
|
||||||
|
it('Spec403 evidence overview exposes canonical proof states without overclaiming operation proof', function (
|
||||||
|
string $status,
|
||||||
|
string $outcome,
|
||||||
|
string $expectedState,
|
||||||
|
): void {
|
||||||
|
$environment = ManagedEnvironment::factory()->create();
|
||||||
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
|
||||||
|
|
||||||
|
$run = OperationRun::factory()->forTenant($environment)->create([
|
||||||
|
'status' => $status,
|
||||||
|
'outcome' => $outcome,
|
||||||
|
'started_at' => now()->subMinutes(5),
|
||||||
|
'completed_at' => $status === OperationRunStatus::Completed->value ? now()->subMinute() : null,
|
||||||
|
'initiator_name' => 'Spec403 Operator',
|
||||||
|
]);
|
||||||
|
|
||||||
|
spec403EvidenceSnapshot($environment, [
|
||||||
|
'operation_run_id' => (int) $run->getKey(),
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'generated_at' => now()->subMinutes(2),
|
||||||
|
'expires_at' => now()->addDay(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$payload = spec403EvidenceOverviewPayload($user, $environment);
|
||||||
|
$operationProof = collect($payload['proof_items'])->firstWhere('label', 'Operation proof');
|
||||||
|
$operationCard = collect($payload['cards'])->firstWhere('label', 'Operation proof');
|
||||||
|
$allowedStates = [
|
||||||
|
'Ready',
|
||||||
|
'Not configured',
|
||||||
|
'Running',
|
||||||
|
'Failed',
|
||||||
|
'Blocked',
|
||||||
|
'Expired',
|
||||||
|
'Needs attention',
|
||||||
|
'Historical',
|
||||||
|
];
|
||||||
|
|
||||||
|
expect($operationProof)->toBeArray()
|
||||||
|
->and($operationProof['state'])->toBe($expectedState)
|
||||||
|
->and($operationProof['url'])->toBeNull()
|
||||||
|
->and($operationProof['actionLabel'])->toBeNull()
|
||||||
|
->and($operationCard)->toBeArray()
|
||||||
|
->and($operationCard['value'])->toBe($expectedState)
|
||||||
|
->and($operationCard['url'])->toBeNull()
|
||||||
|
->and($operationCard['description'])->not->toContain('Operation #')
|
||||||
|
->and($payload['decision_card']['evidence'])->not->toContain('Operation #')
|
||||||
|
->and($operationProof['description'])->toContain('Spec403 Operator')
|
||||||
|
->and(array_values(array_diff(collect($payload['proof_items'])->pluck('state')->all(), $allowedStates)))->toBe([])
|
||||||
|
->and($operationProof['state'])->not->toBe('Available')
|
||||||
|
->and($operationProof['state'])->not->toBe('Unknown');
|
||||||
|
})->with([
|
||||||
|
'running operation' => [
|
||||||
|
OperationRunStatus::Running->value,
|
||||||
|
OperationRunOutcome::Pending->value,
|
||||||
|
'Running',
|
||||||
|
],
|
||||||
|
'succeeded operation' => [
|
||||||
|
OperationRunStatus::Completed->value,
|
||||||
|
OperationRunOutcome::Succeeded->value,
|
||||||
|
'Historical',
|
||||||
|
],
|
||||||
|
'partially succeeded operation' => [
|
||||||
|
OperationRunStatus::Completed->value,
|
||||||
|
OperationRunOutcome::PartiallySucceeded->value,
|
||||||
|
'Needs attention',
|
||||||
|
],
|
||||||
|
'blocked operation' => [
|
||||||
|
OperationRunStatus::Completed->value,
|
||||||
|
OperationRunOutcome::Blocked->value,
|
||||||
|
'Blocked',
|
||||||
|
],
|
||||||
|
'failed operation' => [
|
||||||
|
OperationRunStatus::Completed->value,
|
||||||
|
OperationRunOutcome::Failed->value,
|
||||||
|
'Failed',
|
||||||
|
],
|
||||||
|
'cancelled operation' => [
|
||||||
|
OperationRunStatus::Completed->value,
|
||||||
|
OperationRunOutcome::Cancelled->value,
|
||||||
|
'Failed',
|
||||||
|
],
|
||||||
|
'completed pending operation' => [
|
||||||
|
OperationRunStatus::Completed->value,
|
||||||
|
OperationRunOutcome::Pending->value,
|
||||||
|
'Needs attention',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
it('Spec403 evidence overview uses canonical states for missing proof flow steps', function (): void {
|
||||||
|
$environment = ManagedEnvironment::factory()->create();
|
||||||
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
|
||||||
|
|
||||||
|
$payload = spec403EvidenceOverviewPayload($user, $environment);
|
||||||
|
$states = collect($payload['readiness_flow'])->pluck('state')->all();
|
||||||
|
$proofStates = collect($payload['proof_items'])->pluck('state')->all();
|
||||||
|
$allowedStates = [
|
||||||
|
'Ready',
|
||||||
|
'Not configured',
|
||||||
|
'Running',
|
||||||
|
'Failed',
|
||||||
|
'Blocked',
|
||||||
|
'Expired',
|
||||||
|
'Needs attention',
|
||||||
|
'Historical',
|
||||||
|
];
|
||||||
|
|
||||||
|
expect($states)->toBe([
|
||||||
|
'Ready',
|
||||||
|
'Not configured',
|
||||||
|
'Blocked',
|
||||||
|
'Blocked',
|
||||||
|
'Not configured',
|
||||||
|
'Not configured',
|
||||||
|
])
|
||||||
|
->and(array_values(array_diff($proofStates, $allowedStates)))->toBe([])
|
||||||
|
->and($proofStates)->not->toContain('Available')
|
||||||
|
->and($proofStates)->not->toContain('Unavailable')
|
||||||
|
->and($proofStates)->not->toContain('Not generated')
|
||||||
|
->and($proofStates)->not->toContain('Not applicable')
|
||||||
|
->and($proofStates)->not->toContain('Proof incomplete')
|
||||||
|
->and($proofStates)->not->toContain('Empty')
|
||||||
|
->and($proofStates)->not->toContain('Collapsed')
|
||||||
|
->and($proofStates)->not->toContain('Unknown');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Spec403 evidence overview maps review pack proof lifecycle states canonically', function (
|
||||||
|
string $status,
|
||||||
|
string $expectedState,
|
||||||
|
): void {
|
||||||
|
$environment = ManagedEnvironment::factory()->create();
|
||||||
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
|
||||||
|
|
||||||
|
$snapshot = spec403EvidenceSnapshot($environment, [
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'generated_at' => now()->subMinutes(2),
|
||||||
|
'expires_at' => now()->addDay(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
ReviewPack::factory()->create([
|
||||||
|
'managed_environment_id' => (int) $environment->getKey(),
|
||||||
|
'workspace_id' => (int) $environment->workspace_id,
|
||||||
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
||||||
|
'status' => $status,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$payload = spec403EvidenceOverviewPayload($user, $environment);
|
||||||
|
$reviewPackProof = collect($payload['proof_items'])->firstWhere('label', 'Review pack');
|
||||||
|
$reviewPackCard = collect($payload['cards'])->firstWhere('label', 'Review pack');
|
||||||
|
|
||||||
|
expect($reviewPackProof)->toBeArray()
|
||||||
|
->and($reviewPackProof['state'])->toBe($expectedState)
|
||||||
|
->and($reviewPackCard)->toBeArray()
|
||||||
|
->and($reviewPackCard['value'])->toBe($expectedState)
|
||||||
|
->and($reviewPackProof['state'])->not->toBe('Queued')
|
||||||
|
->and($reviewPackProof['state'])->not->toBe('Generating');
|
||||||
|
})->with([
|
||||||
|
'queued review pack' => [ReviewPackStatus::Queued->value, 'Running'],
|
||||||
|
'generating review pack' => [ReviewPackStatus::Generating->value, 'Running'],
|
||||||
|
'failed review pack' => [ReviewPackStatus::Failed->value, 'Failed'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $attributes
|
||||||
|
*/
|
||||||
|
function spec403EvidenceSnapshot(ManagedEnvironment $environment, array $attributes = []): EvidenceSnapshot
|
||||||
|
{
|
||||||
|
return EvidenceSnapshot::query()->create(array_replace([
|
||||||
|
'managed_environment_id' => (int) $environment->getKey(),
|
||||||
|
'workspace_id' => (int) $environment->workspace_id,
|
||||||
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'summary' => [
|
||||||
|
'dimension_count' => 2,
|
||||||
|
'missing_dimensions' => 0,
|
||||||
|
'stale_dimensions' => 0,
|
||||||
|
],
|
||||||
|
'generated_at' => now(),
|
||||||
|
'expires_at' => now()->addDay(),
|
||||||
|
], $attributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
function spec403EvidenceOverviewPayload(App\Models\User $user, ManagedEnvironment $environment): array
|
||||||
|
{
|
||||||
|
setAdminPanelContext($environment);
|
||||||
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $environment->workspace_id);
|
||||||
|
|
||||||
|
return Livewire::withQueryParams([
|
||||||
|
'environment_id' => (int) $environment->getKey(),
|
||||||
|
])
|
||||||
|
->actingAs($user)
|
||||||
|
->test(EvidenceOverview::class)
|
||||||
|
->instance()
|
||||||
|
->evidenceDisclosurePayload();
|
||||||
|
}
|
||||||
@ -20,6 +20,7 @@
|
|||||||
use App\Models\ProviderConnection;
|
use App\Models\ProviderConnection;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Support\EnvironmentReviewStatus;
|
use App\Support\EnvironmentReviewStatus;
|
||||||
|
use App\Support\Evidence\EvidenceCompletenessState;
|
||||||
use App\Support\Evidence\EvidenceSnapshotStatus;
|
use App\Support\Evidence\EvidenceSnapshotStatus;
|
||||||
use App\Support\Navigation\WorkspaceHubFilterStateResetter;
|
use App\Support\Navigation\WorkspaceHubFilterStateResetter;
|
||||||
use App\Support\Navigation\WorkspaceHubRegistry;
|
use App\Support\Navigation\WorkspaceHubRegistry;
|
||||||
@ -303,7 +304,8 @@ function spec316ClearFilterEvidenceSnapshot(ManagedEnvironment $environment): Ev
|
|||||||
'managed_environment_id' => (int) $environment->getKey(),
|
'managed_environment_id' => (int) $environment->getKey(),
|
||||||
'workspace_id' => (int) $environment->workspace_id,
|
'workspace_id' => (int) $environment->workspace_id,
|
||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
|
'summary' => ['dimension_count' => 5, 'missing_dimensions' => 0, 'stale_dimensions' => 0],
|
||||||
'generated_at' => now(),
|
'generated_at' => now(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -301,7 +301,7 @@ function spec315SeedEnvironmentFilterWorkspace(): array
|
|||||||
'workspace_id' => (int) $environmentA->workspace_id,
|
'workspace_id' => (int) $environmentA->workspace_id,
|
||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
|
'summary' => ['dimension_count' => 5, 'missing_dimensions' => 0, 'stale_dimensions' => 0],
|
||||||
'generated_at' => now(),
|
'generated_at' => now(),
|
||||||
]);
|
]);
|
||||||
$snapshotB = EvidenceSnapshot::query()->create([
|
$snapshotB = EvidenceSnapshot::query()->create([
|
||||||
@ -309,7 +309,7 @@ function spec315SeedEnvironmentFilterWorkspace(): array
|
|||||||
'workspace_id' => (int) $environmentB->workspace_id,
|
'workspace_id' => (int) $environmentB->workspace_id,
|
||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
|
'summary' => ['dimension_count' => 5, 'missing_dimensions' => 0, 'stale_dimensions' => 0],
|
||||||
'generated_at' => now(),
|
'generated_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -76,21 +76,23 @@ function suspendCustomerReviewWorkspacePackAccessWorkspace(ManagedEnvironment $t
|
|||||||
setAdminPanelContext();
|
setAdminPanelContext();
|
||||||
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
||||||
|
|
||||||
Livewire::actingAs($user)
|
$component = Livewire::actingAs($user)
|
||||||
->test(CustomerReviewWorkspace::class)
|
->test(CustomerReviewWorkspace::class)
|
||||||
->assertSee(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review->fresh()], $tenant), false)
|
->assertSee(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review->fresh()], $tenant), false)
|
||||||
->assertSee('Review pack')
|
->assertSee('Review pack')
|
||||||
->assertSee('Available')
|
->assertSee('Ready')
|
||||||
->assertSee('The current review package is available, meets the customer-safe output contract, and can be opened as a rendered report from the review detail.')
|
->assertSee('The current review package is available, meets the customer-safe output contract, and can be opened as a rendered report from the review detail.')
|
||||||
->assertSee('Customer-safe review pack ready')
|
|
||||||
->assertSee('Download customer-safe review pack')
|
->assertSee('Download customer-safe review pack')
|
||||||
->assertSee('source_surface=customer_review_workspace', false)
|
->assertSee('source_surface=customer_review_workspace', false)
|
||||||
->assertSee('tenant_filter_id', false)
|
->assertSee('tenant_filter_id', false)
|
||||||
->assertSee('Open review')
|
->assertSee('Open review')
|
||||||
|
->assertDontSee('Customer-safe review pack ready')
|
||||||
->assertDontSee('Generate pack')
|
->assertDontSee('Generate pack')
|
||||||
->assertDontSee('Regenerate')
|
->assertDontSee('Regenerate')
|
||||||
->assertDontSee('Expire snapshot')
|
->assertDontSee('Expire snapshot')
|
||||||
->assertDontSee('Expire review pack');
|
->assertDontSee('Expire review pack');
|
||||||
|
|
||||||
|
expect($component->html())->toMatch('/<h2[^>]*>\s*Ready\s*<\/h2>/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps the customer review workspace download action visible while suspended read-only', function (): void {
|
it('keeps the customer review workspace download action visible while suspended read-only', function (): void {
|
||||||
@ -165,7 +167,7 @@ function suspendCustomerReviewWorkspacePackAccessWorkspace(ManagedEnvironment $t
|
|||||||
Livewire::actingAs($user)
|
Livewire::actingAs($user)
|
||||||
->test(CustomerReviewWorkspace::class)
|
->test(CustomerReviewWorkspace::class)
|
||||||
->assertSee(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review->fresh()], $tenant), false)
|
->assertSee(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review->fresh()], $tenant), false)
|
||||||
->assertSee('Not available yet')
|
->assertSee('Not configured')
|
||||||
->assertSee('Review Pack has not been generated for this released review yet.')
|
->assertSee('Review Pack has not been generated for this released review yet.')
|
||||||
->assertDontSee('Download review pack')
|
->assertDontSee('Download review pack')
|
||||||
->assertDontSee('Generate pack')
|
->assertDontSee('Generate pack')
|
||||||
@ -207,13 +209,16 @@ function suspendCustomerReviewWorkspacePackAccessWorkspace(ManagedEnvironment $t
|
|||||||
setAdminPanelContext();
|
setAdminPanelContext();
|
||||||
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
||||||
|
|
||||||
Livewire::actingAs($user)
|
$component = Livewire::actingAs($user)
|
||||||
->test(CustomerReviewWorkspace::class)
|
->test(CustomerReviewWorkspace::class)
|
||||||
->assertSee(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review->fresh()], $tenant), false)
|
->assertSee(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $review->fresh()], $tenant), false)
|
||||||
->assertSee('Output not customer-ready')
|
->assertSee('Needs attention')
|
||||||
->assertSee('Review blockers are still recorded for this output.')
|
->assertSee('Review blockers are still recorded for this output.')
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
->assertSee('Available');
|
->assertDontSee('Output not customer-ready')
|
||||||
|
->assertSee('Ready');
|
||||||
|
|
||||||
|
expect($component->html())->toMatch('/<h2[^>]*>\s*Needs attention\s*<\/h2>/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows preparing and unavailable review-pack states without download links', function (): void {
|
it('shows preparing and unavailable review-pack states without download links', function (): void {
|
||||||
@ -255,9 +260,9 @@ function suspendCustomerReviewWorkspacePackAccessWorkspace(ManagedEnvironment $t
|
|||||||
Livewire::actingAs($user)
|
Livewire::actingAs($user)
|
||||||
->test(CustomerReviewWorkspace::class)
|
->test(CustomerReviewWorkspace::class)
|
||||||
->assertCanSeeTableRecords([$preparingTenant->fresh(), $failedTenant->fresh()])
|
->assertCanSeeTableRecords([$preparingTenant->fresh(), $failedTenant->fresh()])
|
||||||
->assertSee('Preparing')
|
->assertSee('Running')
|
||||||
->assertSee('Review Pack is being prepared.')
|
->assertSee('Review Pack is being prepared.')
|
||||||
->assertSee('Unavailable')
|
->assertSee('Failed')
|
||||||
->assertSee('Review Pack cannot be provided right now.')
|
->assertSee('Review Pack cannot be provided right now.')
|
||||||
->assertDontSee('Download review pack');
|
->assertDontSee('Download review pack');
|
||||||
});
|
});
|
||||||
@ -302,7 +307,7 @@ function suspendCustomerReviewWorkspacePackAccessWorkspace(ManagedEnvironment $t
|
|||||||
->test(CustomerReviewWorkspace::class)
|
->test(CustomerReviewWorkspace::class)
|
||||||
->assertCanSeeTableRecords([$expiredTenant->fresh(), $blockedTenant->fresh()])
|
->assertCanSeeTableRecords([$expiredTenant->fresh(), $blockedTenant->fresh()])
|
||||||
->assertSee('Expired')
|
->assertSee('Expired')
|
||||||
->assertSee('Unavailable')
|
->assertSee('Blocked')
|
||||||
->assertDontSee('Download review pack');
|
->assertDontSee('Download review pack');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -205,17 +205,17 @@
|
|||||||
->assertSee('Expiring soon')
|
->assertSee('Expiring soon')
|
||||||
->assertSee('Expired')
|
->assertSee('Expired')
|
||||||
->assertSee('Pending approval')
|
->assertSee('Pending approval')
|
||||||
->assertSee('Needs review')
|
->assertSee('Needs attention')
|
||||||
->assertDontSee('Operation proof')
|
->assertDontSee('Operation proof')
|
||||||
->assertSee('Customer-safe follow-ups')
|
->assertSee('Customer-safe follow-ups')
|
||||||
->assertSee('Review package index')
|
->assertSee('Review package index')
|
||||||
->assertSee('Disclosure rule')
|
->assertSee('Disclosure rule')
|
||||||
->assertSee('Decision')
|
->assertSee('Decision')
|
||||||
->assertSee('Visible')
|
->assertSee('Ready')
|
||||||
->assertSee('Diagnostics')
|
->assertSee('Diagnostics')
|
||||||
->assertSee('Collapsed')
|
->assertSee('Not configured')
|
||||||
->assertSee('Raw/support')
|
->assertSee('Raw/support')
|
||||||
->assertSee('Hidden')
|
->assertSee('Blocked')
|
||||||
->assertSee('Support details stay on authorized diagnostic surfaces')
|
->assertSee('Support details stay on authorized diagnostic surfaces')
|
||||||
->assertSee('Customer acceptance checkpoint')
|
->assertSee('Customer acceptance checkpoint')
|
||||||
->assertSee('Open review')
|
->assertSee('Open review')
|
||||||
@ -296,14 +296,14 @@
|
|||||||
Livewire::actingAs($user)
|
Livewire::actingAs($user)
|
||||||
->test(CustomerReviewWorkspace::class)
|
->test(CustomerReviewWorkspace::class)
|
||||||
->assertSee('What is the current review pack output state?')
|
->assertSee('What is the current review pack output state?')
|
||||||
->assertSee('Published with limitations')
|
->assertSee('Needs attention')
|
||||||
->assertSee('Accepted-risk follow-up is recorded for this review')
|
->assertSee('Accepted-risk follow-up is recorded for this review')
|
||||||
->assertSee('The pack can be shared only with the accepted-risk context included in the customer handoff.')
|
->assertSee('The pack can be shared only with the accepted-risk context included in the customer handoff.')
|
||||||
->assertSee('Needs review')
|
->assertSee('Needs attention')
|
||||||
->assertSee('Follow-up required')
|
->assertSee('Follow-up required')
|
||||||
->assertSee('Accepted-risk follow-up is required.')
|
->assertSee('Accepted-risk follow-up is required.')
|
||||||
->assertSee('Open review')
|
->assertSee('Open review')
|
||||||
->assertSeeInOrder(['Published with limitations', 'Open review'])
|
->assertSeeInOrder(['Needs attention', 'Open review'])
|
||||||
->assertDontSee('Download internal preview')
|
->assertDontSee('Download internal preview')
|
||||||
->assertDontSee('Ready to share');
|
->assertDontSee('Ready to share');
|
||||||
});
|
});
|
||||||
@ -365,15 +365,18 @@
|
|||||||
setAdminPanelContext();
|
setAdminPanelContext();
|
||||||
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
|
||||||
|
|
||||||
Livewire::actingAs($user)
|
$component = Livewire::actingAs($user)
|
||||||
->test(CustomerReviewWorkspace::class)
|
->test(CustomerReviewWorkspace::class)
|
||||||
->assertSee('What is the current review pack output state?')
|
->assertSee('What is the current review pack output state?')
|
||||||
->assertSee('Output not customer-ready')
|
->assertSee('Needs attention')
|
||||||
->assertSee('Review blockers are still recorded for this output.')
|
->assertSee('Review blockers are still recorded for this output.')
|
||||||
->assertDontSee('No operation proof linked')
|
->assertDontSee('No operation proof linked')
|
||||||
->assertSee('Available')
|
->assertSee('Ready')
|
||||||
|
->assertDontSee('Output not customer-ready')
|
||||||
->assertDontSee('Ready to share')
|
->assertDontSee('Ready to share')
|
||||||
->assertDontSee('Download internal preview');
|
->assertDontSee('Download internal preview');
|
||||||
|
|
||||||
|
expect($component->html())->toMatch('/<h2[^>]*>\s*Needs attention\s*<\/h2>/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows the current released review using deterministic published review ordering', function (): void {
|
it('shows the current released review using deterministic published review ordering', function (): void {
|
||||||
@ -570,7 +573,7 @@
|
|||||||
->assertSee('Accepted risks')
|
->assertSee('Accepted risks')
|
||||||
->assertSee('Accepted risk')
|
->assertSee('Accepted risk')
|
||||||
->assertSee('Included in the released review evidence basis.')
|
->assertSee('Included in the released review evidence basis.')
|
||||||
->assertSee('Needs review')
|
->assertSee('Needs attention')
|
||||||
->assertSee('Open review')
|
->assertSee('Open review')
|
||||||
->assertDontSee('Ready for release')
|
->assertDontSee('Ready for release')
|
||||||
->assertSee('Risk Owner')
|
->assertSee('Risk Owner')
|
||||||
@ -605,7 +608,7 @@
|
|||||||
Livewire::actingAs($user)
|
Livewire::actingAs($user)
|
||||||
->test(CustomerReviewWorkspace::class)
|
->test(CustomerReviewWorkspace::class)
|
||||||
->assertSee('Decision trail')
|
->assertSee('Decision trail')
|
||||||
->assertSee('Unavailable')
|
->assertSee('Not configured')
|
||||||
->assertSee('Customer-safe decision evidence is unavailable for this released review.')
|
->assertSee('Customer-safe decision evidence is unavailable for this released review.')
|
||||||
->assertDontSee('raw evidence JSON')
|
->assertDontSee('raw evidence JSON')
|
||||||
->assertDontSee('legacy-fingerprint-abc123')
|
->assertDontSee('legacy-fingerprint-abc123')
|
||||||
|
|||||||
@ -264,7 +264,7 @@ function spec393EvidenceSnapshot(ManagedEnvironment $tenant, array $attributes =
|
|||||||
'workspace_id' => (int) $tenant->workspace_id,
|
'workspace_id' => (int) $tenant->workspace_id,
|
||||||
'status' => EvidenceSnapshotStatus::Active->value,
|
'status' => EvidenceSnapshotStatus::Active->value,
|
||||||
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
||||||
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
|
'summary' => ['dimension_count' => 5, 'missing_dimensions' => 0, 'stale_dimensions' => 0],
|
||||||
'fingerprint' => 'spec393-'.str()->uuid()->toString(),
|
'fingerprint' => 'spec393-'.str()->uuid()->toString(),
|
||||||
'generated_at' => now(),
|
'generated_at' => now(),
|
||||||
], $attributes));
|
], $attributes));
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 338 KiB |
@ -0,0 +1,79 @@
|
|||||||
|
# Requirements Checklist: Spec 403 - Evidence Anchor & Currentness Runtime Closure
|
||||||
|
|
||||||
|
**Purpose**: Validate preparation quality for Spec 403 before implementation starts.
|
||||||
|
**Created**: 2026-06-23
|
||||||
|
**Feature**: `specs/403-evidence-anchor-currentness-runtime-closure/spec.md`
|
||||||
|
|
||||||
|
## Candidate Selection
|
||||||
|
|
||||||
|
- [x] The selected candidate was directly provided by the operator.
|
||||||
|
- [x] The candidate is linked to Spec 400 evidence/currentness risk and Spec 402's recommended next action.
|
||||||
|
- [x] `docs/product/spec-candidates.md` was reviewed and currently reports no safe automatic next-best-prep target.
|
||||||
|
- [x] Close alternatives are deferred instead of hidden inside the primary scope.
|
||||||
|
- [x] The target does not reopen completed Specs 388, 393, 400, 401, or 402.
|
||||||
|
- [x] No existing `specs/403-evidence-anchor-currentness-runtime-closure/` package existed before preparation.
|
||||||
|
- [x] Spec 402 prerequisite is recorded as `PASS` with no P0/P1 authorization blocker.
|
||||||
|
|
||||||
|
## Scope Quality
|
||||||
|
|
||||||
|
- [x] The spec is bounded to existing evidence/currentness proof and minimal hardening.
|
||||||
|
- [x] No new product vocabulary, surfaces, navigation, routes, migrations, report/PDF runtime, provider integration, or broad audit is included.
|
||||||
|
- [x] Evidence Overview and EvidenceSnapshotResource are explicitly in scope.
|
||||||
|
- [x] Customer Review Workspace, review packs, environment reviews, and stored reports are explicitly in scope.
|
||||||
|
- [x] OperationRun proof links and authorization/scoping are explicitly in scope.
|
||||||
|
- [x] Baseline, restore, finding/governance, and report evidence references are explicitly in scope where existing surfaces are repo-real.
|
||||||
|
- [x] Customer-safe boundary, current/released distinction, and workspace/environment isolation are explicit.
|
||||||
|
- [x] Provider freshness and permission-limited evidence-currentness contracts are covered where repo-real.
|
||||||
|
- [x] Management PDF staging validation, governance lifecycle/retention, JSONB migration, and full browser audit are deferred.
|
||||||
|
|
||||||
|
## Constitution And Product Surface
|
||||||
|
|
||||||
|
- [x] Spec Candidate Check is filled out.
|
||||||
|
- [x] Approval class is exactly one class: Core Enterprise.
|
||||||
|
- [x] Score is recorded and above the minimum threshold.
|
||||||
|
- [x] Proportionality Review is completed and forbids new runtime framework/state/persistence by default.
|
||||||
|
- [x] No runtime source of truth, persisted table, enum/status family, taxonomy, or broad proof framework is introduced.
|
||||||
|
- [x] Product Surface Contract is referenced because rendered evidence/status/report/customer-safe behavior may change.
|
||||||
|
- [x] UI Surface Impact is classified as existing-surface evidence/currentness hardening only.
|
||||||
|
- [x] UI coverage registry review/update is required for touched existing surfaces when runtime UI files or reachable evidence/status semantics change.
|
||||||
|
- [x] Browser proof is required for representative rendered evidence/currentness behavior.
|
||||||
|
- [x] Human Product Sanity is required for customer-safe/readiness/evidence surfaces.
|
||||||
|
- [x] Completed-spec rewrite guardrail is explicit.
|
||||||
|
|
||||||
|
## Plan Quality
|
||||||
|
|
||||||
|
- [x] Plan identifies Laravel, Filament, Livewire, Pest, Sail, and PostgreSQL context.
|
||||||
|
- [x] Plan names panel provider registration location.
|
||||||
|
- [x] Plan names likely affected repository surfaces.
|
||||||
|
- [x] Plan requires matrix-first work before runtime fixes.
|
||||||
|
- [x] Plan distinguishes evidence truth, released/report truth, OperationRun proof truth, restore/baseline/finding proof references, customer-safe boundary, and authorization.
|
||||||
|
- [x] Plan requires existing evidence/proof helpers to remain authoritative unless matrix/tests prove a gap.
|
||||||
|
- [x] Plan requires route-inventory and design-coverage-matrix review/update when runtime UI files or reachable evidence/status semantics change.
|
||||||
|
- [x] Plan forbids new framework/status/persistence/product vocabulary by default.
|
||||||
|
- [x] Plan includes rollout/deployment impact and expects no migrations/env/assets/queues/storage changes.
|
||||||
|
|
||||||
|
## Task Quality
|
||||||
|
|
||||||
|
- [x] Tasks are ordered by preparation, inventory, matrix, gap classification, tests, hardening, product sanity, browser proof, and report close-out.
|
||||||
|
- [x] Tasks require tests before runtime fixes where feasible.
|
||||||
|
- [x] Tasks include current evidence, stale/missing/failed/partial, released/current, customer-safe, OperationRun, cross-workspace, and cross-environment proof.
|
||||||
|
- [x] Tasks include restore, baseline, finding/governance, report, and review proof tasks where applicable.
|
||||||
|
- [x] Tasks include provider freshness / permission-limited state inventory, matrix classification, and tests where repo-real.
|
||||||
|
- [x] Tasks include Filament v5 output contract close-out fields for Livewire, provider registration, global search, destructive/high-impact actions, assets, tests/browser, and deployment.
|
||||||
|
- [x] Tasks include focused browser proof and explicitly forbid claiming full browser audit.
|
||||||
|
- [x] Tasks include dirty-state protocol before and after implementation.
|
||||||
|
- [x] Tasks include final implementation report sections A through M.
|
||||||
|
- [x] Tasks include non-goals that prevent scope creep.
|
||||||
|
|
||||||
|
## Open Questions And Readiness
|
||||||
|
|
||||||
|
- [x] No open question blocks implementation preparation.
|
||||||
|
- [x] Missing product decisions are required to be reported rather than invented.
|
||||||
|
- [x] Spec Readiness Gate can pass after artifact analysis.
|
||||||
|
- [x] Candidate Selection Gate passes as a manual operator-promoted candidate.
|
||||||
|
|
||||||
|
## Review Outcome
|
||||||
|
|
||||||
|
- [x] Review outcome class: `acceptable-special-case` for a bounded evidence/currentness runtime closure spec.
|
||||||
|
- [x] Workflow outcome: `keep`.
|
||||||
|
- [x] Final note location: future implementation report `specs/403-evidence-anchor-currentness-runtime-closure/implementation-report.md`.
|
||||||
@ -0,0 +1,313 @@
|
|||||||
|
# Spec 403 Implementation Report
|
||||||
|
|
||||||
|
## A. Candidate Gate Result
|
||||||
|
|
||||||
|
**Result**: PASS
|
||||||
|
|
||||||
|
Spec 403 found and closed bounded runtime/product-contract defects in Evidence Overview and Customer Review Workspace: rendered proof/status states and status-like decision-card titles used non-canonical availability/export labels, Review Pack proof could surface queued/generating lifecycle labels instead of `Running`, linked `OperationRun` proof could be overclaimed as available/current artifact evidence, raw `Operation #...` identifiers and default OperationRun links were visible in the default proof path, and complete snapshots with missing, stale, or empty/no-usable dimensions were not explicitly excluded from current-anchor selection. Current evidence anchors are now constrained to scoped, active, complete snapshots with usable captured dimensions, no missing/stale dimensions, non-expired timing, and actor authorization.
|
||||||
|
|
||||||
|
No P0 or P1 evidence/currentness blockers remain for the touched high-risk Evidence Overview/current-anchor, Review Pack proof, OperationRun proof, or Customer Review Workspace status surfaces. Broad full-browser and downstream lifecycle audits remain out of scope and are recorded as P2/deferred follow-up, not as completed work.
|
||||||
|
|
||||||
|
## B. Included And Not Included Scope
|
||||||
|
|
||||||
|
Included:
|
||||||
|
|
||||||
|
- Evidence/currentness inventory for existing helpers, Evidence Overview, evidence snapshot access, customer/review/report proof surfaces, OperationRun proof links, restore, baseline, finding/governance references, and provider freshness contracts.
|
||||||
|
- Minimal runtime correction in `EvidenceOverview` proof state labels, Evidence Inventory outcome-state mapping, Review Pack proof state mapping, OperationRun proof state mapping and default-link/identifier demotion, `EvidenceAnchorResolver` current-anchor missing/stale/empty-dimension filtering, and Evidence Snapshot artifact-truth classification for missing dimensions.
|
||||||
|
- Minimal runtime correction in `CustomerReviewWorkspace` visible state labels, status-like decision-card titles, and customer-review Blade fallbacks so customer-safe/released review status surfaces use `Ready`, `Not configured`, `Running`, `Failed`, `Blocked`, `Expired`, and `Needs attention` instead of `Available`, `Unavailable`, `Collapsed`, `Not ready`, `Needs review`, `Customer-safe review pack ready`, `Output not customer-ready`, `Internal review package available`, `Published with limitations`, or export-specific status labels. Non-status action headings such as `Draft review exists` remain allowed and are not treated as canonical status vocabulary.
|
||||||
|
- Focused Feature/Filament tests for current anchor validity, invalid snapshot states, canonical proof labels, Review Pack lifecycle states, OperationRun outcomes, and Customer Review Workspace status language.
|
||||||
|
- Existing regression updates for Evidence Overview, Customer Review Workspace, Spec 337 readiness flow, Spec 342 consumption, Spec 326 browser smoke, and Spec 329 disclosure expectations.
|
||||||
|
- Focused Pest browser proof for failed OperationRun proof without a default OperationRun URL or raw identifier, expired evidence current-link denial, stale-dimension current-link denial, missing-dimension current-link denial, Customer Review Workspace canonical rendered states, customer-safe output boundaries, and cross-workspace environment denial.
|
||||||
|
- Product Surface Contract close-out for the touched Evidence Overview and Customer Review Workspace surfaces.
|
||||||
|
|
||||||
|
Not included:
|
||||||
|
|
||||||
|
- New routes, navigation, panels, resources, report/PDF runtime, customer output category, evidence provider, persisted entity, enum/status family, taxonomy, migration, package, env var, queue/scheduler/storage change, or asset registration.
|
||||||
|
- Broad UI/browser audit for all evidence-adjacent surfaces.
|
||||||
|
- Management-report PDF staging validation or provider readiness productization.
|
||||||
|
- Rewriting completed historical specs or changing closed validation history.
|
||||||
|
|
||||||
|
## C. Dirty State And Baseline
|
||||||
|
|
||||||
|
Starting branch: `403-evidence-anchor-currentness-runtime-closure`
|
||||||
|
|
||||||
|
Starting HEAD: `c5db3ea4 feat: add resource policy authorization proof matrix (#473)`
|
||||||
|
|
||||||
|
Starting dirty state:
|
||||||
|
|
||||||
|
- Untracked active spec package: `specs/403-evidence-anchor-currentness-runtime-closure/`
|
||||||
|
- No unrelated tracked dirty files were reset or cleaned.
|
||||||
|
|
||||||
|
Current changed files:
|
||||||
|
|
||||||
|
- `apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php`
|
||||||
|
- `apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php`
|
||||||
|
- `apps/platform/app/Services/Evidence/EvidenceAnchorResolver.php`
|
||||||
|
- `apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php`
|
||||||
|
- `apps/platform/resources/views/filament/pages/monitoring/evidence-overview.blade.php`
|
||||||
|
- `apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php`
|
||||||
|
- `apps/platform/tests/Browser/Spec316WorkspaceHubClearFilterSmokeTest.php`
|
||||||
|
- `apps/platform/tests/Browser/Spec326CustomerReviewWorkspaceProductizationSmokeTest.php`
|
||||||
|
- `apps/platform/tests/Browser/Spec337EvidenceReviewPackProductFlowSmokeTest.php`
|
||||||
|
- `apps/platform/tests/Browser/Spec342CustomerReviewWorkspaceConsumptionSmokeTest.php`
|
||||||
|
- `apps/platform/tests/Browser/Spec347ReviewPackOutputReadinessSmokeTest.php`
|
||||||
|
- `apps/platform/tests/Browser/Spec349OutputResolutionGuidanceSmokeTest.php`
|
||||||
|
- `apps/platform/tests/Browser/Spec350OperatorResolutionGuidanceSmokeTest.php`
|
||||||
|
- `apps/platform/tests/Browser/Spec351ReviewOutputResolveActionsSmokeTest.php`
|
||||||
|
- `apps/platform/tests/Browser/Spec385EvidenceReviewReadinessSmokeTest.php`
|
||||||
|
- `apps/platform/tests/Browser/Spec392CustomerOutputGatingSmokeTest.php`
|
||||||
|
- `apps/platform/tests/Browser/Support/Spec322WorkspaceEnvironmentBrowserHarness.php`
|
||||||
|
- `apps/platform/tests/Browser/Spec403EvidenceCurrentnessRuntimeClosureSmokeTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Evidence/EvidenceOverviewPageTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Filament/Spec337EvidenceReviewPackProductFlowTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Filament/Spec342CustomerReviewWorkspaceConsumptionTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Filament/Spec347CustomerReviewWorkspaceOutputReadinessTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Filament/Spec349CustomerReviewWorkspaceOutputGuidanceTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Filament/Spec350CustomerReviewWorkspaceGuidanceIntegrationTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Filament/Spec385CustomerReviewWorkspaceBaselineReadinessTest.php`
|
||||||
|
- `apps/platform/tests/Feature/ManagedEnvironments/AuthorizationSemanticsTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Monitoring/EvidenceOverviewWorkspaceHubContractTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Monitoring/Spec329EvidenceAuditDisclosureProductizationTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Monitoring/Spec403EvidenceCurrentnessRuntimeClosureTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Navigation/WorkspaceHubClearFilterContractTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Navigation/WorkspaceHubEnvironmentFilterContractTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php`
|
||||||
|
- `apps/platform/tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php`
|
||||||
|
- `apps/platform/tests/Unit/Evidence/Spec393EvidenceAnchorResolverTest.php`
|
||||||
|
- `specs/403-evidence-anchor-currentness-runtime-closure/artifacts/screenshots/.gitkeep`
|
||||||
|
- `specs/403-evidence-anchor-currentness-runtime-closure/artifacts/screenshots/spec403-evidence-currentness-operation-proof-failed.png`
|
||||||
|
- `specs/403-evidence-anchor-currentness-runtime-closure/checklists/requirements.md`
|
||||||
|
- `specs/403-evidence-anchor-currentness-runtime-closure/implementation-report.md`
|
||||||
|
- `specs/403-evidence-anchor-currentness-runtime-closure/plan.md`
|
||||||
|
- `specs/403-evidence-anchor-currentness-runtime-closure/spec.md`
|
||||||
|
- `specs/403-evidence-anchor-currentness-runtime-closure/tasks.md`
|
||||||
|
|
||||||
|
`git diff --check`: PASS before runtime edits and PASS during close-out.
|
||||||
|
|
||||||
|
## D. Evidence/Currentness Coverage Matrix
|
||||||
|
|
||||||
|
| Surface | Evidence Source | Currentness Source | Released Snapshot Source | Customer-safe Boundary | Internal-only Risk | Workspace/Environment Scope | Authorization Mechanism | Test Proof | Browser Proof | Status | Risk | Follow-up |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| EvidenceAnchorResolver current anchor | `EvidenceSnapshot` | active, complete, usable captured dimensions, no missing/stale dimensions, non-expired, scoped snapshot | N/A | No raw payload surfaced | Wrong-scope links | Workspace plus optional environment | `EvidenceSnapshotPolicy`, environment entitlement | `Spec403EvidenceCurrentnessRuntimeClosureTest` invalid-state dataset | Covered through Evidence Overview focused browser path | PASS | None | None |
|
||||||
|
| Evidence Overview workspace-wide | Snapshot rows and anchor resolver | No arbitrary current snapshot without selected scope; row URLs require the same snapshot ID as the current anchor | N/A | Diagnostics collapsed | Raw snapshot/provider payload | Active workspace only | Workspace membership plus row URL authorization | Existing Evidence Overview regressions and Spec 403 tests | Existing Spec 329/337 smoke plus Spec 403 smoke | PASS | None | None |
|
||||||
|
| Evidence Overview environment-filtered readiness flow | Latest scoped snapshot, stored report, review pack, review, operation | Canonical states: `Ready`, `Not configured`, `Running`, `Failed`, `Blocked`, `Expired`, `Needs attention`, `Historical` | Published review/current export pack where present | Customer-safe state is summary only | Raw report/evidence diagnostics | Environment filter must belong to active workspace | 404 on foreign environment | Spec 337 and Spec 403 feature tests | Spec 337 and Spec 403 browser smoke | PASS | None | None |
|
||||||
|
| Evidence Overview OperationRun proof | Linked `OperationRun` from current proof path | Operation status/outcome, not artifact currentness | N/A | Not used in customer-safe output | Default OperationRun URL exposure | Linked run workspace/environment | Technical operation routes remain policy-gated; Evidence Overview does not emit the URL by default | Spec 403 outcome dataset plus URL-null assertions | Spec 403 failed run smoke verifies no OperationRun anchor | PASS | None | None |
|
||||||
|
| Evidence Snapshot resource | `EvidenceSnapshot` and items | Snapshot status/completeness/expiry | N/A | Support/internal detail page, not customer default | Raw payload/detail disclosure | Environment-bound routes | Resource policy and environment entitlement | Existing resource tests plus Spec 403 anchor tests | Existing evidence snapshot browser history | PASS WITH EXCEPTION | P2 | No runtime change; broader detail browser audit remains separate |
|
||||||
|
| Customer Review Workspace | Released review, current export pack, customer-safe summaries | Released review/package state, not live current evidence | `EnvironmentReview` and `ReviewPack` release binding | Raw evidence, IDs, source keys, fingerprints, OperationRun URLs hidden by default | Customer leakage | Workspace/environment filters | Workspace membership and environment entitlement | CustomerReviewWorkspace, PackAccess, Spec342, Spec347, Spec349, Spec350, and Spec385 tests prove canonical visible states, status-like decision-card titles, and customer-safe boundaries; Spec351 preserves `Draft review exists` as a non-status action heading | Spec326, Spec342, Spec347, Spec349, Spec350, Spec351, Spec385, and Spec392 browser smoke prove canonical rendered states, allowed non-status action headings, and hidden technical proof | PASS | None | None |
|
||||||
|
| Environment Review, Review Pack, Stored Report | Review evidence snapshot, review pack, stored report | Generated/released artifact state | Review/report generation basis | Default views demote internal proof | Fingerprints/raw metadata | Environment-bound records | Resource policies and signed/current-export checks | Existing Spec 388, EnvironmentReview, ReviewPack, StoredReport tests | Existing Spec 337/347/372/397 browser history | PASS WITH EXCEPTION | P2 | No runtime change; management-report PDF staging remains Spec 404 candidate |
|
||||||
|
| OperationRun direct proof links | `OperationRun` records | Run status/outcome only | N/A | Not emitted on customer-safe default paths or Evidence Overview default proof path | Cross-workspace run visibility | Run workspace/environment | `OperationRunPolicy`, tenantless viewer checks, Spec 402 authorization matrix | Existing TenantlessOperationRunViewer and Spec 402 proof; Spec 403 new outcome mapping | Spec 403 failed proof status and default-link demotion | PASS | None | None |
|
||||||
|
| Restore readiness/proof | Restore preview/run readiness truth | Existing restore gates and run status | N/A | Operator safety surface | Destructive action proof | Environment-bound | Restore policies and confirmation flows | Existing Spec 390/restore tests | Existing Spec 333/335 browser history | PASS WITH EXCEPTION | P2 | No runtime change; full restore failure/conflict audit deferred |
|
||||||
|
| Baseline compare/evidence | Baseline snapshots, compare results, local evidence | Existing baseline readiness/evidence contracts | N/A | Operator/governance surface | Diagnostic evidence gaps | Workspace/environment | Baseline policies and environment entitlement | Existing baseline evidence/readiness tests | Existing Spec 336/369/384 browser history | PASS WITH EXCEPTION | P2 | No runtime change; compare matrix hierarchy remains separate |
|
||||||
|
| Finding/governance references | Finding exceptions, decisions, evidence references | Current validity and reference availability | Decision/review basis where applicable | No direct evidence link without access | Evidence link leakage | Workspace/environment | Governance policies and evidence destination access | Existing governance authorization/reference tests | Existing governance browser history | PASS WITH EXCEPTION | P2 | Finding detail productization remains separate |
|
||||||
|
| Provider freshness/permission-limited state | Provider diagnostics where already connected to evidence quality | Existing evidence contracts only | N/A | Provider diagnostics stay internal/support | Provider payload leakage | Workspace/environment | Provider/resource policies | Existing provider/evidence freshness tests where repo-real | N/A for this runtime change | DEFERRED | P2 | Provider readiness/onboarding productization follow-up |
|
||||||
|
|
||||||
|
## E. Runtime Changes
|
||||||
|
|
||||||
|
- Replaced non-canonical readiness/proof states in Evidence Overview with the existing Product Surface vocabulary.
|
||||||
|
- Split expired evidence from stale/missing/empty-dimension evidence in internal state helpers and current-anchor filtering. Current-anchor surfaces filter all of them out before linking, so expired, stale-dimension, missing-dimension, or no-usable-content snapshots render as not current/linkable rather than current proof.
|
||||||
|
- Reclassified complete snapshots with missing dimensions as partial artifact truth and mapped Evidence Overview row outcomes to canonical `Ready`/`Needs attention`, preventing workspace-wide rows from presenting missing/empty evidence as trustworthy/current.
|
||||||
|
- Changed stored-report and missing-operation proof cards from `Available`/`Unavailable` to `Ready`/`Not configured`.
|
||||||
|
- Mapped Review Pack proof lifecycle states in Evidence Overview directly to canonical states, so queued and generating packs render as `Running` rather than `Queued` or `Generating`.
|
||||||
|
- Mapped OperationRun proof to `Running`, `Historical`, `Needs attention`, `Blocked`, or `Failed` from existing run status/outcome.
|
||||||
|
- Demoted OperationRun URLs and raw `Operation #...` identifiers from the default Evidence Overview proof path; OperationRun records remain technical history/proof and direct operation routes remain policy-gated.
|
||||||
|
- Replaced Customer Review Workspace visible status labels, status-like decision-card titles, and Blade fallbacks with canonical product states, including review-pack availability, evidence/disclosure rows, customer-safe output steps, internal export availability, findings follow-up, accepted-risk status rows, latest-review/package badges, and resolution-case titles that previously rendered `Customer-safe review pack ready`, `Output not customer-ready`, `Internal review package available`, or `Published with limitations`. Kept non-status action headings such as `Draft review exists` outside the canonical status-vocabulary requirement.
|
||||||
|
- Kept all Graph/provider calls out of render-time code paths.
|
||||||
|
- Did not change resolver workspace/environment/authorization scope rules, routes, policies, migrations, assets, or providers.
|
||||||
|
|
||||||
|
## F. Tests Added Or Updated
|
||||||
|
|
||||||
|
Added:
|
||||||
|
|
||||||
|
- `tests/Feature/Monitoring/Spec403EvidenceCurrentnessRuntimeClosureTest.php`
|
||||||
|
- Invalid current-anchor dataset: queued, generating, failed, partial, missing dimensions, no usable captured dimensions, stale dimensions, expired, superseded.
|
||||||
|
- Positive current-anchor proof: active, complete, no missing/stale dimensions, non-expired, scoped evidence.
|
||||||
|
- OperationRun outcome mapping: running, succeeded, partially succeeded, blocked, failed, cancelled, and completed-pending, with no default OperationRun URL on Evidence Overview proof items/cards.
|
||||||
|
- OperationRun default summary and proof card assertions reject raw `Operation #...` identifiers.
|
||||||
|
- Review Pack proof lifecycle mapping proves queued/generating statuses render as `Running` and failed packs render as `Failed`.
|
||||||
|
- Missing proof-flow and proof-item canonical state checks, including rejection of legacy `Available`, `Unavailable`, `Not generated`, `Not applicable`, `Proof incomplete`, `Empty`, `Collapsed`, and `Unknown` proof states.
|
||||||
|
- `tests/Browser/Spec403EvidenceCurrentnessRuntimeClosureSmokeTest.php`
|
||||||
|
- Failed OperationRun proof renders `Failed`, not `Ready` or successful current evidence, and emits no default OperationRun anchor or raw `Operation #...` identifier.
|
||||||
|
- Expired and stale-dimension evidence cannot render an internal current evidence link and keep the Evidence snapshot step `Not configured`.
|
||||||
|
- Missing-dimension evidence renders canonical `Needs attention`, does not render `Partially complete` or `Trustworthy artifact`, and cannot render an internal current evidence link.
|
||||||
|
- Cross-workspace environment filter denies access with 404.
|
||||||
|
|
||||||
|
Updated:
|
||||||
|
|
||||||
|
- `Spec337EvidenceReviewPackProductFlowTest.php`
|
||||||
|
- `Spec337EvidenceReviewPackProductFlowSmokeTest.php`
|
||||||
|
- `CustomerReviewWorkspacePageTest.php`
|
||||||
|
- `CustomerReviewWorkspacePackAccessTest.php`
|
||||||
|
- `Spec342CustomerReviewWorkspaceConsumptionTest.php`
|
||||||
|
- `Spec342CustomerReviewWorkspaceConsumptionSmokeTest.php`
|
||||||
|
- `Spec347CustomerReviewWorkspaceOutputReadinessTest.php`
|
||||||
|
- `Spec347ReviewPackOutputReadinessSmokeTest.php`
|
||||||
|
- `Spec349CustomerReviewWorkspaceOutputGuidanceTest.php`
|
||||||
|
- `Spec349OutputResolutionGuidanceSmokeTest.php`
|
||||||
|
- `Spec350CustomerReviewWorkspaceGuidanceIntegrationTest.php`
|
||||||
|
- `Spec350OperatorResolutionGuidanceSmokeTest.php`
|
||||||
|
- `Spec351ReviewOutputResolveActionsSmokeTest.php`
|
||||||
|
- `Spec326CustomerReviewWorkspaceProductizationSmokeTest.php`
|
||||||
|
- `Spec385CustomerReviewWorkspaceBaselineReadinessTest.php`
|
||||||
|
- `Spec385EvidenceReviewReadinessSmokeTest.php`
|
||||||
|
- `Spec392CustomerOutputGatingSmokeTest.php`
|
||||||
|
- `EvidenceOverviewPageTest.php`
|
||||||
|
- `Spec329EvidenceAuditDisclosureProductizationTest.php`
|
||||||
|
|
||||||
|
## G. Focused Browser Proof
|
||||||
|
|
||||||
|
Command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/platform && ./vendor/bin/sail artisan test tests/Browser/Spec403EvidenceCurrentnessRuntimeClosureSmokeTest.php tests/Browser/Spec342CustomerReviewWorkspaceConsumptionSmokeTest.php tests/Browser/Spec326CustomerReviewWorkspaceProductizationSmokeTest.php --compact
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS, 5 tests, 181 assertions.
|
||||||
|
|
||||||
|
Decision-card closure command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/platform && ./vendor/bin/sail artisan test tests/Browser/Spec342CustomerReviewWorkspaceConsumptionSmokeTest.php tests/Browser/Spec347ReviewPackOutputReadinessSmokeTest.php tests/Browser/Spec349OutputResolutionGuidanceSmokeTest.php tests/Browser/Spec350OperatorResolutionGuidanceSmokeTest.php tests/Browser/Spec351ReviewOutputResolveActionsSmokeTest.php tests/Browser/Spec385EvidenceReviewReadinessSmokeTest.php tests/Browser/Spec392CustomerOutputGatingSmokeTest.php --compact
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS, 7 tests, 228 assertions.
|
||||||
|
|
||||||
|
Route/surface: `/admin/evidence/overview?environment_id=...`, Evidence Overview; Customer Review Workspace filtered and unfiltered customer-safe review routes.
|
||||||
|
|
||||||
|
Actor: workspace owner/manager or read-only customer-review actor in entitled workspace/environment contexts.
|
||||||
|
|
||||||
|
Evidence states: active complete snapshot linked to a failed `OperationRun`; expired active snapshot for a separate entitled environment; active complete snapshot with stale dimensions for a separate entitled environment; active complete snapshot with missing dimensions for a separate entitled environment; foreign environment filter; released customer review with ready export; customer review with incomplete/customer-follow-up output.
|
||||||
|
|
||||||
|
Expected result: Operation proof badge shows `Failed` without a default OperationRun anchor or raw `Operation #...` identifier; raw payload/provider/stack-trace text stays hidden; diagnostics are demoted without a non-canonical visible status; expired and stale-dimension evidence render no internal current evidence link; missing-dimension evidence renders as canonical `Needs attention` rather than `Partially complete` or `Trustworthy artifact` and no internal current evidence link; Customer Review Workspace badges, steps, and status-like decision-card H2 titles render canonical states such as `Ready`, `Not configured`, and `Needs attention`; old status titles such as `Customer-safe review pack ready`, `Output not customer-ready`, `Internal review package available`, and `Requires review` are absent from the rendered Workspace decision path; non-status action headings such as `Draft review exists` remain allowed; customer-safe paths do not expose OperationRun proof; a foreign environment filter returns 404 and does not leak the foreign environment name.
|
||||||
|
|
||||||
|
Observed result: PASS.
|
||||||
|
|
||||||
|
Console/runtime errors: none via `assertNoJavaScriptErrors()` and `assertNoConsoleLogs()`.
|
||||||
|
|
||||||
|
Screenshot: `specs/403-evidence-anchor-currentness-runtime-closure/artifacts/screenshots/spec403-evidence-currentness-operation-proof-failed.png`.
|
||||||
|
|
||||||
|
Additional rendered regression:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Filament/Spec337EvidenceReviewPackProductFlowTest.php tests/Browser/Spec337EvidenceReviewPackProductFlowSmokeTest.php --compact
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS, 7 tests, 133 assertions.
|
||||||
|
|
||||||
|
## H. Current Vs Released Proof Summary
|
||||||
|
|
||||||
|
- Current runtime evidence remains constrained by `EvidenceAnchorResolver::currentForScope()` and never promotes queued, generating, failed, partial, missing-dimension, empty/no-usable-content, stale-dimension, expired, superseded, wrong-scope, or missing snapshots into current links.
|
||||||
|
- Workspace-wide Evidence Overview rows now classify missing-dimension and empty snapshots as canonical `Needs attention`, so they cannot appear as trustworthy/current evidence even though the row remains available for operator follow-up.
|
||||||
|
- Evidence Overview Review Pack proof now maps queued/generating artifact lifecycle to `Running`, ready artifacts to `Ready`, failed artifacts to `Failed`, expired artifacts to `Expired`, and unknown non-ready states to `Blocked`; it no longer bypasses the Evidence Overview vocabulary through the generic ReviewPack status badge.
|
||||||
|
- Evidence Overview now labels runtime proof states without implying that a succeeded `OperationRun` is current evidence. A succeeded run is `Historical`; failed and partial outcomes are not shown as ready/current proof, and OperationRun URLs are not emitted from the default proof path.
|
||||||
|
- Released/customer-safe review and report surfaces keep released output tied to its released/generated basis rather than arbitrary latest runtime evidence. Customer Review Workspace visible states are canonical product states and do not claim released proof is live/current runtime evidence.
|
||||||
|
|
||||||
|
## I. Customer-Safe Boundary Summary
|
||||||
|
|
||||||
|
- Evidence Overview remains an internal operator surface with diagnostics collapsed, raw evidence/provider strings hidden, and OperationRun URLs demoted out of the default proof path.
|
||||||
|
- Customer Review Workspace, Review Pack, and rendered report behavior were inventoried as existing customer-safe boundaries. No default customer-safe path emits raw EvidenceSnapshot routes, source keys, fingerprints, provider payloads, OperationRun URLs, raw OperationRun identifiers, stack traces, raw exception messages, or internal-only diagnostics.
|
||||||
|
- Spec 326, Spec 342, and Spec 337 browser proof verifies customer-safe/export states after canonical label changes.
|
||||||
|
|
||||||
|
## J. Remaining Findings
|
||||||
|
|
||||||
|
- P0: none.
|
||||||
|
- P1: none for touched Evidence Overview/current-anchor behavior, Review Pack proof lifecycle mapping, OperationRun default proof demotion, or Customer Review Workspace canonical status language.
|
||||||
|
- P2: broader downstream browser/productization coverage remains deferred for Evidence Snapshot detail, restore failures/conflicts, baseline compare matrix hierarchy, finding detail productization, provider readiness/onboarding, and non-touched legacy status language in other historical/deferred product surfaces.
|
||||||
|
- P3: none recorded.
|
||||||
|
|
||||||
|
## K. Deferred Items
|
||||||
|
|
||||||
|
- Spec 404: Management Report PDF Staging Validation.
|
||||||
|
- Governance artifact lifecycle/retention runtime.
|
||||||
|
- JSONB payload conversion and indexing for queryable evidence/report/audit payloads.
|
||||||
|
- Full browser/UX/runtime audit if later productization requires broad coverage.
|
||||||
|
- Provider readiness/onboarding productization.
|
||||||
|
- Broader restore failure/conflict browser coverage.
|
||||||
|
- Baseline compare matrix and finding detail productization passes.
|
||||||
|
|
||||||
|
## L. Validation Commands
|
||||||
|
|
||||||
|
Completed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Monitoring/Spec403EvidenceCurrentnessRuntimeClosureTest.php tests/Feature/Evidence/EvidenceOverviewPageTest.php tests/Feature/Monitoring/EvidenceOverviewWorkspaceHubContractTest.php tests/Feature/Evidence/EvidenceSnapshotResourceTest.php tests/Feature/Filament/Spec337EvidenceReviewPackProductFlowTest.php tests/Feature/Monitoring/Spec329EvidenceAuditDisclosureProductizationTest.php tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php tests/Feature/Filament/Spec342CustomerReviewWorkspaceConsumptionTest.php --compact
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS, 82 tests, 811 assertions.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/platform && ./vendor/bin/sail artisan test tests/Unit/Evidence/Spec393EvidenceAnchorResolverTest.php tests/Feature/Navigation/WorkspaceHubClearFilterContractTest.php tests/Feature/Navigation/WorkspaceHubEnvironmentFilterContractTest.php tests/Feature/ManagedEnvironments/AuthorizationSemanticsTest.php tests/Feature/Monitoring/EvidenceOverviewWorkspaceHubContractTest.php --compact
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS, 25 tests, 523 assertions.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/platform && ./vendor/bin/sail artisan test tests/Browser/Spec403EvidenceCurrentnessRuntimeClosureSmokeTest.php tests/Browser/Spec342CustomerReviewWorkspaceConsumptionSmokeTest.php tests/Browser/Spec326CustomerReviewWorkspaceProductizationSmokeTest.php --compact
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS, 5 tests, 181 assertions.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Filament/Spec337EvidenceReviewPackProductFlowTest.php tests/Browser/Spec337EvidenceReviewPackProductFlowSmokeTest.php --compact
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS, 7 tests, 133 assertions.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/platform && ./vendor/bin/sail artisan test tests/Browser/Spec316WorkspaceHubClearFilterSmokeTest.php tests/Browser/Spec338ScopeContractSmokeTest.php tests/Browser/Spec322WorkspaceHubNoDriftSmokeTest.php tests/Browser/Spec340PostScopeContractVerificationSmokeTest.php --compact
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS, 10 tests, 798 assertions.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Filament/Spec342CustomerReviewWorkspaceConsumptionTest.php tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php tests/Feature/Filament/Spec347CustomerReviewWorkspaceOutputReadinessTest.php tests/Feature/Filament/Spec349CustomerReviewWorkspaceOutputGuidanceTest.php tests/Feature/Filament/Spec350CustomerReviewWorkspaceGuidanceIntegrationTest.php tests/Feature/Filament/Spec385CustomerReviewWorkspaceBaselineReadinessTest.php --compact
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS, 34 tests, 349 assertions.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/platform && ./vendor/bin/sail artisan test tests/Browser/Spec342CustomerReviewWorkspaceConsumptionSmokeTest.php tests/Browser/Spec347ReviewPackOutputReadinessSmokeTest.php tests/Browser/Spec349OutputResolutionGuidanceSmokeTest.php tests/Browser/Spec350OperatorResolutionGuidanceSmokeTest.php tests/Browser/Spec351ReviewOutputResolveActionsSmokeTest.php tests/Browser/Spec385EvidenceReviewReadinessSmokeTest.php tests/Browser/Spec392CustomerOutputGatingSmokeTest.php --compact
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS, 7 tests, 228 assertions.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Monitoring/Spec403EvidenceCurrentnessRuntimeClosureTest.php tests/Browser/Spec403EvidenceCurrentnessRuntimeClosureSmokeTest.php --compact
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS, 21 tests, 242 assertions.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff --check
|
||||||
|
```
|
||||||
|
|
||||||
|
Result: PASS after runtime, test, browser, and report updates.
|
||||||
|
|
||||||
|
## M. Recommended Next Action
|
||||||
|
|
||||||
|
Spec 403 may proceed toward review. The next product candidate can be Spec 404 only after reviewers accept this PASS and its P2 deferred items as outside the current runtime closure scope.
|
||||||
|
|
||||||
|
## Filament V5 And Product Surface Close-Out
|
||||||
|
|
||||||
|
- Livewire v4.0+ compliance: PASS. Runtime and tests target Livewire 4.1.4; no Livewire v3 APIs were introduced.
|
||||||
|
- No-legacy posture: PASS. Legacy availability/proof labels are not kept as compatibility aliases on the touched Evidence Overview or Customer Review Workspace proof/status surfaces. Non-status action headings such as `Draft review exists` are not status vocabulary and remain outside the no-legacy status-language claim.
|
||||||
|
- Provider registration location: unchanged. Laravel 12 panel providers remain registered through `apps/platform/bootstrap/providers.php`; no provider registration was edited.
|
||||||
|
- Global search: unchanged. `EvidenceSnapshotResource` already has a View page and no global-search behavior was added. Evidence Overview and Customer Review Workspace are pages, not globally searchable resources.
|
||||||
|
- Destructive/high-impact actions: no new actions were added. Existing destructive/high-impact action confirmation and authorization posture was not changed.
|
||||||
|
- Asset strategy: no assets were added or registered. No new `filament:assets` deployment requirement was introduced beyond the existing deploy process.
|
||||||
|
- Product Surface Impact: touched existing Evidence Overview and Customer Review Workspace reachable proof/status semantics.
|
||||||
|
- UI Surface Impact: canonical readiness/proof, Evidence Inventory outcome vocabulary, Review Pack proof lifecycle vocabulary, Customer Review Workspace status vocabulary, and OperationRun default-proof demotion; no route, navigation, layout, report, PDF, or new customer surface.
|
||||||
|
- Page archetype: Evidence Overview is a `Dashboard Page` with Technical Annex/deep-link demotion for diagnostic proof; Customer Review Workspace remains a customer-safe `Report Page`/review consumption surface.
|
||||||
|
- Surface budgets: no new cards, actions, tables, or navigation. Visible complexity is neutral; status vocabulary is simpler, and default OperationRun deep links/raw identifiers were removed from the proof path.
|
||||||
|
- Technical Annex/deep-link demotion: diagnostics remain collapsed/demoted; OperationRun URLs and raw `Operation #...` identifiers are not emitted by the default proof path; raw/internal proof is still demoted to authorized detail pages.
|
||||||
|
- Product Surface exceptions: none.
|
||||||
|
- Focused browser proof: PASS, Spec 403, Spec 342, Spec 326, Spec 337, Spec 347, Spec 349, Spec 350, Spec 351, Spec 385, Spec 392, and workspace scope commands above. Spec 403 covers failed OperationRun proof without a default OperationRun anchor or raw identifier, expired evidence current-link denial, stale-dimension current-link denial, missing-dimension canonical `Needs attention` row labeling, and cross-scope denial; Spec 342/326/347/349/350/385/392 cover Customer Review Workspace canonical rendered states and status-like decision-card titles; Spec 351 covers `Draft review exists` as an allowed non-status action heading; Spec 337 covers customer-safe/export and released review-pack states.
|
||||||
|
- Human Product Sanity: PASS. Touched UI text now distinguishes current evidence, operation history/failure, missing configuration, customer-safe readiness, and released review-pack state without adding extra visible surface area.
|
||||||
|
- UI coverage registry: `docs/ui-ux-enterprise-audit/route-inventory.md` and `design-coverage-matrix.md` were reviewed. Existing Evidence Overview/UI-044, customer workspace/UI-038, review pack/UI-042, rendered report/UI-099, evidence snapshot/UI-045/UI-046, restore, baseline, and finding entries remain structurally current because this implementation changed labels/semantics only.
|
||||||
|
- Implementation-report fields: tests, browser/no-browser, Livewire v4, provider registration, global search, destructive/high-impact actions, asset strategy, and deployment impact are recorded here.
|
||||||
|
- Deployment impact: no migrations, env vars, queues, scheduler, storage, assets, routes, panel providers, or Graph/provider runtime changes.
|
||||||
279
specs/403-evidence-anchor-currentness-runtime-closure/plan.md
Normal file
279
specs/403-evidence-anchor-currentness-runtime-closure/plan.md
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
# Implementation Plan: Spec 403 - Evidence Anchor & Currentness Runtime Closure
|
||||||
|
|
||||||
|
**Branch**: `403-evidence-anchor-currentness-runtime-closure` | **Date**: 2026-06-23 | **Spec**: `specs/403-evidence-anchor-currentness-runtime-closure/spec.md`
|
||||||
|
**Input**: Feature specification from `specs/403-evidence-anchor-currentness-runtime-closure/spec.md`
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Spec 403 closes the remaining evidence-anchor and currentness product-contract gap before TenantPilot expands management-report or customer-output claims. The implementation must inventory existing evidence/currentness claims, build an Evidence/Currentness Coverage Matrix, add focused tests and browser proof, and apply only minimal runtime fixes where a claim is false, unsafe, unscoped, or unproven.
|
||||||
|
|
||||||
|
Do not start by creating a new proof framework. Use the matrix to decide whether existing `EvidenceAnchorResolver`, `EvidenceAnchorResult`, Spec 388 proof-currentness support, policies, and shared presenters already express the required truth. Extend existing paths only when the matrix proves a concrete gap.
|
||||||
|
|
||||||
|
## Technical Context
|
||||||
|
|
||||||
|
**Language/Version**: PHP 8.4.15, Laravel 12.52.0
|
||||||
|
**Primary Dependencies**: Filament 5.2.1, Livewire 4.1.4, Laravel Sail, Pest 4.3.1, PHPUnit 12.5.4
|
||||||
|
**Storage**: PostgreSQL; no schema changes allowed by default
|
||||||
|
**Testing**: Pest Unit/Feature/Filament/Browser tests
|
||||||
|
**Validation Lanes**: confidence for Feature/Filament proof; browser for representative rendered evidence/currentness proof; optional heavy-governance only if a matrix guard is implemented
|
||||||
|
**Target Platform**: TenantPilot Laravel monolith under `apps/platform`
|
||||||
|
**Project Type**: Laravel + Filament application
|
||||||
|
**Performance Goals**: No Graph/provider calls during render; no broad full-suite/browser expansion; no material test-runtime cost without report note
|
||||||
|
**Constraints**: No new product surfaces, routes, navigation, persisted truth, new status vocabulary, report runtime, provider integration, migration, asset registration, or broad runtime audit
|
||||||
|
**Scale/Scope**: Existing evidence/currentness surfaces in admin/customer/review/report/restore/baseline/finding/operation flows
|
||||||
|
|
||||||
|
## Preparation Context
|
||||||
|
|
||||||
|
- Dirty state before preparation: clean on `platform-dev` at `c5db3ea4 feat: add resource policy authorization proof matrix (#473)`.
|
||||||
|
- Spec Kit script used: `.specify/scripts/bash/create-new-feature.sh --json --number 403 --short-name evidence-anchor-currentness-runtime-closure "..."`
|
||||||
|
- Generated branch/path: `403-evidence-anchor-currentness-runtime-closure`, `specs/403-evidence-anchor-currentness-runtime-closure/`.
|
||||||
|
- Candidate source: direct user-provided Spec 403 draft, promoted after Spec 402's `PASS` close-out and recommendation.
|
||||||
|
- Active auto-prep queue: `docs/product/spec-candidates.md` reports no safe automatic next-best-prep target, so this is a manual operator-promoted candidate.
|
||||||
|
- Completed-spec guardrail: Specs 388, 393, 400, 401, and 402 are read-only context. Do not rewrite their close-out, validation, completed tasks, implementation reports, or browser evidence.
|
||||||
|
|
||||||
|
## Existing Repository Surfaces
|
||||||
|
|
||||||
|
### Evidence and currentness foundations
|
||||||
|
|
||||||
|
- `apps/platform/app/Services/Evidence/EvidenceAnchorResolver.php`
|
||||||
|
- `apps/platform/app/Services/Evidence/EvidenceAnchorResult.php`
|
||||||
|
- `apps/platform/app/Services/Evidence/EvidenceSnapshotResolver.php`
|
||||||
|
- `apps/platform/app/Services/Evidence/EvidenceSnapshotService.php`
|
||||||
|
- `apps/platform/app/Support/Evidence/EvidenceSnapshotStatus.php`
|
||||||
|
- `apps/platform/app/Support/Evidence/EvidenceCompletenessState.php`
|
||||||
|
- `apps/platform/app/Models/EvidenceSnapshot.php`
|
||||||
|
- `apps/platform/app/Models/EvidenceSnapshotItem.php`
|
||||||
|
|
||||||
|
### Existing rendered/admin/customer surfaces to inventory
|
||||||
|
|
||||||
|
- `apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php`
|
||||||
|
- `apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php`
|
||||||
|
- `apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php`
|
||||||
|
- `apps/platform/app/Filament/Resources/EnvironmentReviewResource.php`
|
||||||
|
- `apps/platform/app/Filament/Resources/EnvironmentReviewResource/Pages/ResolveReviewPublication.php`
|
||||||
|
- `apps/platform/resources/views/filament/resources/environment-review-resource/pages/resolve-review-publication.blade.php`
|
||||||
|
- `apps/platform/app/Filament/Resources/ReviewPackResource.php`
|
||||||
|
- `apps/platform/app/Filament/Resources/StoredReportResource.php`
|
||||||
|
- `apps/platform/app/Filament/Resources/RestoreRunResource.php` and restore presenter/proof Blade views
|
||||||
|
- `apps/platform/app/Filament/Pages/BaselineCompareMatrix.php`
|
||||||
|
- `apps/platform/app/Filament/Resources/BaselineSnapshotResource.php`
|
||||||
|
- `apps/platform/app/Filament/Resources/BaselineProfileResource.php`
|
||||||
|
- `apps/platform/app/Filament/Resources/FindingResource.php`
|
||||||
|
- `apps/platform/app/Filament/Resources/FindingExceptionResource.php`
|
||||||
|
- Monitoring/Operations and OperationRun detail/link helpers where proof links are emitted
|
||||||
|
|
||||||
|
### Existing proof and output semantics
|
||||||
|
|
||||||
|
- Spec 388 proof-currentness classes under `apps/platform/app/Support/ReviewPublicationResolution/`.
|
||||||
|
- Spec 393 evidence anchor resolver/result behavior.
|
||||||
|
- Review/report output builders and services discovered by implementation, including management-report payload builders where they are already repo-real.
|
||||||
|
- Restore readiness/safety support and baseline evidence providers.
|
||||||
|
- Existing policies for evidence, reviews, review packs, OperationRuns, findings, and restore/baseline resources.
|
||||||
|
|
||||||
|
## UI / Surface Guardrail Plan
|
||||||
|
|
||||||
|
- **Guardrail scope**: existing rendered evidence/currentness/status/proof presentation may be corrected; no new surfaces.
|
||||||
|
- **Affected routes/pages/actions/states/navigation/panel/provider surfaces**: exact touched surfaces named in implementation report after matrix inventory.
|
||||||
|
- **No-impact class, if applicable**: N/A because rendered evidence/status presentation may change.
|
||||||
|
- **Native vs custom classification summary**: native Filament resources/pages plus existing Blade views and shared helpers.
|
||||||
|
- **Shared-family relevance**: evidence/report viewers, status messaging, action links, OperationRun proof links, customer-safe output, readiness/proof labels.
|
||||||
|
- **State layers in scope**: page, detail, table row, report output, proof link, wizard/detail proof section, route/query context.
|
||||||
|
- **Audience modes in scope**: customer/read-only, operator-MSP, support-platform where existing surfaces permit support/raw evidence.
|
||||||
|
- **Decision/diagnostic/raw hierarchy plan**: default product surfaces show product proof state; technical proof remains secondary/internal; support/raw remains capability-gated.
|
||||||
|
- **Raw/support gating plan**: preserve or tighten existing authorization. No raw evidence links in customer-safe default views.
|
||||||
|
- **One-primary-action / duplicate-truth control**: no new primary actions; technical links remain secondary.
|
||||||
|
- **Handling modes by drift class or surface**: hard-stop for P0 false/leaking claims; review-mandatory for P1 missing proof; document-in-feature for bounded implementation choices; follow-up-spec for structural product decisions.
|
||||||
|
- **Repository-signal treatment**: review-mandatory for arbitrary latest evidence selectors, direct evidence routes in customer-safe output, OperationRun link leakage, current/released copy conflicts, stale/failed/partial proof overclaims, and cross-scope links.
|
||||||
|
- **Special surface test profiles**: shared-detail-family, monitoring-state-page, customer-safe report surface, restore wizard/detail, baseline decision surface.
|
||||||
|
- **Required tests or manual smoke**: Unit/Feature/Filament tests plus focused browser smoke.
|
||||||
|
- **Exception path and spread control**: any Product Surface exception must be in the implementation report with page, violated rule/budget, reason, and follow-up.
|
||||||
|
- **Active feature PR close-out entry**: `Guardrail / Exception / Smoke Coverage`.
|
||||||
|
- **UI/Productization coverage decision**: existing-surface evidence/currentness hardening only; route-inventory/design-matrix review or update is required if runtime UI files or reachable evidence/status semantics change.
|
||||||
|
- **Coverage artifacts to update**: review/update `docs/ui-ux-enterprise-audit/route-inventory.md` and `docs/ui-ux-enterprise-audit/design-coverage-matrix.md` for touched existing surfaces if runtime UI files or reachable evidence/status semantics change. If existing entries already cover the final archetype/depth/safety state, record that review result in the implementation report.
|
||||||
|
- **Navigation / Filament provider-panel handling**: no panel provider or navigation change. Providers remain in `apps/platform/bootstrap/providers.php`.
|
||||||
|
- **Screenshot or page-report need**: no page-report set; focused browser proof is sufficient.
|
||||||
|
|
||||||
|
## Product Surface Contract Plan
|
||||||
|
|
||||||
|
- **Product Surface Contract reference**: `docs/product/standards/product-surface-contract.md`
|
||||||
|
- **No-legacy posture**: canonical current evidence/currentness behavior, no compatibility exception.
|
||||||
|
- **Page archetype and surface budget plan**: existing archetypes only; no added default OperationRun/raw evidence/source-key links.
|
||||||
|
- **Technical Annex and deep-link demotion plan**: OperationRun, evidence IDs, source keys, detectors, fingerprints, raw payloads, and technical logs remain secondary/internal.
|
||||||
|
- **Canonical status vocabulary plan**: map internal evidence states to existing canonical product states; do not introduce new top-level product states.
|
||||||
|
- **Product Surface exceptions**: none planned.
|
||||||
|
- **UI coverage registry plan**: runtime UI changes require route-inventory/design-coverage-matrix review or update for touched existing surfaces before close-out.
|
||||||
|
- **Browser verification plan**: focused paths for admin evidence, customer-safe output, released evidence, stale/missing/failed state, unauthorized/cross-scope denial, and OperationRun proof link visibility/blocking.
|
||||||
|
- **Human Product Sanity plan**: 5-15 minute review of touched customer-safe/readiness/evidence surfaces after implementation.
|
||||||
|
- **Visible complexity outcome target**: neutral or decreased.
|
||||||
|
- **Implementation report target**: `specs/403-evidence-anchor-currentness-runtime-closure/implementation-report.md`.
|
||||||
|
|
||||||
|
## Filament / Livewire / Deployment Posture
|
||||||
|
|
||||||
|
- **Livewire v4 compliance**: Livewire 4.1.4 confirmed by Laravel Boost; no Livewire v3 APIs allowed.
|
||||||
|
- **Panel provider registration location**: unchanged; Laravel 12 panel providers are registered in `apps/platform/bootstrap/providers.php`.
|
||||||
|
- **Global search posture**: no resource global-search participation is added or changed by default. If evidence/report resources are touched, confirm View/Edit pages and `$recordTitleAttribute` or global-search disabled posture.
|
||||||
|
- **Destructive/high-impact action posture**: no new destructive/high-impact action is planned. Existing restore/report/review actions keep `->action(...)`, confirmation, authorization, audit, and tests where already required.
|
||||||
|
- **Asset strategy**: no new assets. `filament:assets` is not newly required unless implementation unexpectedly registers assets, which is out of scope by default.
|
||||||
|
- **Testing plan**: focused Unit/Feature/Filament tests for evidence/currentness truth, RBAC/scope, customer-safe boundary, OperationRun proof links, current/released semantics, plus focused browser smoke.
|
||||||
|
- **Deployment impact**: no env vars, migrations, queues, scheduler, storage, assets, routes, panels, or navigation changes expected. Deployment notes should record no infrastructure impact unless implementation discovers otherwise and spec/plan are updated first.
|
||||||
|
|
||||||
|
## Shared Pattern & System Fit
|
||||||
|
|
||||||
|
- **Cross-cutting feature marker**: yes.
|
||||||
|
- **Systems touched**: evidence anchor resolution, review/report output, OperationRun proof links, baseline/restore proof surfaces, customer-safe views, existing badges/presenters.
|
||||||
|
- **Shared abstractions reused**: `EvidenceAnchorResolver`, `EvidenceAnchorResult`, `EvidenceSnapshotStatus`, `EvidenceCompletenessState`, Spec 388 proof-currentness support, `OperationRunLinks`, policies, and existing ArtifactTruth/Badge paths.
|
||||||
|
- **New abstraction introduced? why?**: none planned.
|
||||||
|
- **Why the existing abstraction was sufficient or insufficient**: existing paths are the correct current repo truth; this spec tests and corrects usage. If insufficient, update spec/plan before adding structure.
|
||||||
|
- **Bounded deviation / spread control**: local label/link corrections are allowed for confirmed false claims; recurring semantics become follow-up spec rather than new framework inside Spec 403.
|
||||||
|
|
||||||
|
## OperationRun UX Impact
|
||||||
|
|
||||||
|
- **Touches OperationRun start/completion/link UX?**: proof/link visibility only; no new start/completion behavior.
|
||||||
|
- **Central contract reused**: existing OperationRun UX/link/policy helpers.
|
||||||
|
- **Delegated UX behaviors**: unchanged.
|
||||||
|
- **Surface-owned behavior kept local**: existing proof display only.
|
||||||
|
- **Queued DB-notification policy**: unchanged.
|
||||||
|
- **Terminal notification path**: unchanged.
|
||||||
|
- **Exception path**: none planned.
|
||||||
|
|
||||||
|
## Provider Boundary & Portability Fit
|
||||||
|
|
||||||
|
- **Shared provider/platform boundary touched?**: possibly where provider freshness/permissions affect evidence claims.
|
||||||
|
- **Provider-owned seams**: provider connection/readiness/permission diagnostic detail.
|
||||||
|
- **Platform-core seams**: evidence proof, current/released status, customer-safe output, OperationRun proof, workspace/environment scope.
|
||||||
|
- **Neutral platform terms / contracts preserved**: workspace, managed environment, provider, evidence, proof, review, report, operation, current, released, historical, customer-safe.
|
||||||
|
- **Retained provider-specific semantics and why**: Microsoft/Graph/Entra terms remain in existing provider-owned diagnostics only.
|
||||||
|
- **Bounded extraction or follow-up path**: provider productization remains separate if needed.
|
||||||
|
|
||||||
|
## Constitution Check
|
||||||
|
|
||||||
|
- Inventory-first: PASS planned. The matrix starts from observed repo/runtime evidence, not speculative product concepts.
|
||||||
|
- Read/write separation: PASS planned. No new write flow; existing restore/report/review actions retain safety gates.
|
||||||
|
- Graph contract path: PASS planned. No new Graph calls; UI render remains DB-only.
|
||||||
|
- Deterministic capabilities: PASS planned. Existing capability/policy paths remain authoritative.
|
||||||
|
- RBAC-UX: PASS planned. Scope and authorization for evidence/OperationRun links are explicit; non-members remain deny-as-not-found.
|
||||||
|
- Workspace isolation: PASS planned. Cross-workspace proof denial tests are required.
|
||||||
|
- Tenant/managed-environment isolation: PASS planned. Evidence and proof references must match workspace and managed environment.
|
||||||
|
- Run observability: PASS planned. Existing OperationRun proof only; no new run type.
|
||||||
|
- OperationRun start UX: PASS planned. No new local start UX.
|
||||||
|
- Ops-UX lifecycle: PASS planned. No new status/outcome transitions.
|
||||||
|
- Data minimization: PASS planned. No secrets/raw provider payloads in customer output, tests, reports, or logs.
|
||||||
|
- Test governance: PASS planned. Confidence/browser lanes explicit; heavy-governance not hidden.
|
||||||
|
- Proportionality: PASS planned. No new persisted truth or broad framework by default.
|
||||||
|
- No premature abstraction: PASS planned. Extend existing paths only after matrix proof.
|
||||||
|
- Persisted truth: PASS. No new persistence.
|
||||||
|
- Behavioral state: PASS. No new state family.
|
||||||
|
- UI semantics: PASS. No new UI taxonomy.
|
||||||
|
- Shared pattern first: PASS. Existing evidence/proof helpers reused.
|
||||||
|
- Provider boundary: PASS. Provider-specific semantics stay bounded.
|
||||||
|
- V1 explicitness / few layers: PASS. Targeted tests and fixes only.
|
||||||
|
- Spec discipline / bloat check: PASS. One coherent closure spec instead of many micro-specs.
|
||||||
|
- Filament-native UI: PASS planned. Existing Filament surfaces remain native/shared-first.
|
||||||
|
- UI/Productization coverage: PASS planned. Existing-surface presentation hardening only.
|
||||||
|
- Product Surface Contract Gate: PASS planned. Browser proof, human sanity, no-legacy, and implementation-report fields are explicit.
|
||||||
|
|
||||||
|
## Test Governance Check
|
||||||
|
|
||||||
|
- **Test purpose / classification by changed surface**: Unit for resolver/state mapping; Feature/Filament for evidence/report/review/restore/baseline scope and display; Browser for focused rendered proof; Heavy-Governance only if a matrix/discovery guard is added explicitly.
|
||||||
|
- **Affected validation lanes**: confidence, browser, optional heavy-governance.
|
||||||
|
- **Why this lane mix is the narrowest sufficient proof**: evidence/currentness truth is business behavior plus rendered product wording; full browser audit is unnecessary and out of scope.
|
||||||
|
- **Narrowest proving command(s)**:
|
||||||
|
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec403`
|
||||||
|
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Evidence/EvidenceOverviewPageTest.php tests/Feature/Evidence/EvidenceSnapshotResourceTest.php --compact`
|
||||||
|
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/EnvironmentReview/Spec388ReviewPublicationProofCurrentnessTest.php --compact`
|
||||||
|
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php --compact`
|
||||||
|
- targeted restore/baseline/finding/report tests changed by implementation
|
||||||
|
- focused Spec 403 browser smoke
|
||||||
|
- `git diff --check`
|
||||||
|
- formatter for changed PHP files
|
||||||
|
- **Fixture / helper / factory / seed / context cost risks**: evidence/review/report/OperationRun/restore/baseline fixtures can become expensive. Keep new helpers opt-in and named for Spec 403 proof.
|
||||||
|
- **Expensive defaults or shared helper growth introduced?**: no by default.
|
||||||
|
- **Heavy-family additions, promotions, or visibility changes**: none planned.
|
||||||
|
- **Surface-class relief / special coverage rule**: standard-native Filament relief for unchanged resource mechanics; special coverage for customer-safe, monitoring-state, and proof-link surfaces.
|
||||||
|
- **Closing validation and reviewer handoff**: implementation report records command results, browser proof, human sanity, residual findings, and next action.
|
||||||
|
- **Budget / baseline / trend follow-up**: none expected; document if test runtime grows materially.
|
||||||
|
- **Review-stop questions**: Did every critical false-claim risk get a test? Did any customer-safe view expose raw proof? Did any current/released label conflict? Did any OperationRun link bypass authorization? Did any new abstraction slip in without updated proportionality review?
|
||||||
|
- **Escalation path**: document-in-feature for bounded residuals; follow-up-spec for structural product decisions; reject-or-split for broad audit/report/lifecycle creep.
|
||||||
|
- **Active feature PR close-out entry**: `Guardrail / Exception / Smoke Coverage`.
|
||||||
|
- **Why no dedicated follow-up spec is needed**: Spec 403 is the bounded closure package; only unresolved P0/P1 findings need remediation before Spec 404.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
### Documentation (this feature)
|
||||||
|
|
||||||
|
```text
|
||||||
|
specs/403-evidence-anchor-currentness-runtime-closure/
|
||||||
|
|-- spec.md
|
||||||
|
|-- plan.md
|
||||||
|
|-- tasks.md
|
||||||
|
|-- checklists/
|
||||||
|
| `-- requirements.md
|
||||||
|
`-- implementation-report.md # created during implementation
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source Code (repository root)
|
||||||
|
|
||||||
|
Implementation may inspect and, only if confirmed by matrix/tests, minimally edit:
|
||||||
|
|
||||||
|
```text
|
||||||
|
apps/platform/app/Services/Evidence/
|
||||||
|
apps/platform/app/Support/Evidence/
|
||||||
|
apps/platform/app/Support/ReviewPublicationResolution/
|
||||||
|
apps/platform/app/Support/OpsUx/
|
||||||
|
apps/platform/app/Support/Operations/
|
||||||
|
apps/platform/app/Filament/Pages/Monitoring/
|
||||||
|
apps/platform/app/Filament/Pages/Reviews/
|
||||||
|
apps/platform/app/Filament/Resources/
|
||||||
|
apps/platform/app/Filament/Resources/*/Pages/
|
||||||
|
apps/platform/resources/views/filament/
|
||||||
|
apps/platform/app/Http/Controllers/
|
||||||
|
apps/platform/routes/ # inspect-only for existing route authorization/scoping; route edits require spec/plan update
|
||||||
|
apps/platform/tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Explicitly Out Of Scope
|
||||||
|
|
||||||
|
```text
|
||||||
|
database/migrations/
|
||||||
|
composer.json
|
||||||
|
package.json
|
||||||
|
apps/platform/config/
|
||||||
|
new routes/pages/navigation
|
||||||
|
new panel providers
|
||||||
|
new report/PDF runtime
|
||||||
|
new evidence providers
|
||||||
|
new governance lifecycle runtime
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complexity Tracking
|
||||||
|
|
||||||
|
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||||
|
|---|---|---|
|
||||||
|
| None planned | N/A | N/A |
|
||||||
|
|
||||||
|
## Proportionality Review
|
||||||
|
|
||||||
|
- **Current operator problem**: evidence-backed claims can mislead operators or customers if current/released/stale/missing/failed/partial/internal/customer-safe meanings drift.
|
||||||
|
- **Existing structure is insufficient because**: unknown until matrix/test inventory. Current assumption is existing structures are sufficient and need targeted usage/hardening.
|
||||||
|
- **Narrowest correct implementation**: matrix, tests, minimal existing-path fixes, focused browser proof.
|
||||||
|
- **Ownership cost created**: focused tests, implementation report, and any small helper extension.
|
||||||
|
- **Alternative intentionally rejected**: new evidence taxonomy, new proof framework, new currentness enum family, new customer output category, full browser audit, management-report runtime changes.
|
||||||
|
- **Release truth**: current-release product trust closure.
|
||||||
|
|
||||||
|
## Implementation Phases
|
||||||
|
|
||||||
|
1. Preparation and dirty-state baseline.
|
||||||
|
2. Repo truth inventory and Evidence/Currentness Coverage Matrix.
|
||||||
|
3. Gap classification and severity.
|
||||||
|
4. Tests first for current evidence, released/customer-safe evidence, OperationRun proof, and cross-scope denial.
|
||||||
|
5. Minimal runtime closure using existing helpers and surfaces.
|
||||||
|
6. Focused browser proof and human product sanity.
|
||||||
|
7. Implementation report, validation commands, residual findings, and recommended next action.
|
||||||
|
|
||||||
|
## Risk Controls
|
||||||
|
|
||||||
|
- Stop and update spec/plan before adding persistence, new routes, new product states, broad helper frameworks, provider integrations, report/PDF runtime, or lifecycle semantics.
|
||||||
|
- Stop and classify as product-decision required if existing contracts do not define customer-safe proof behavior.
|
||||||
|
- Do not claim browser proof unless focused browser tests actually run.
|
||||||
|
- Do not rewrite completed historical specs.
|
||||||
|
- Do not claim Spec 404 readiness unless Spec 403 final result allows it.
|
||||||
473
specs/403-evidence-anchor-currentness-runtime-closure/spec.md
Normal file
473
specs/403-evidence-anchor-currentness-runtime-closure/spec.md
Normal file
@ -0,0 +1,473 @@
|
|||||||
|
# Feature Specification: Spec 403 - Evidence Anchor & Currentness Runtime Closure
|
||||||
|
|
||||||
|
**Feature Branch**: `403-evidence-anchor-currentness-runtime-closure`
|
||||||
|
**Created**: 2026-06-23
|
||||||
|
**Status**: Draft / Ready for implementation preparation review
|
||||||
|
**Type**: Targeted product-contract hardening / evidence proof / runtime closure spec
|
||||||
|
**Runtime posture**: Existing evidence/currentness correctness, proof display, tests, and focused browser validation only. No new surfaces, no new product vocabulary, no broad runtime audit.
|
||||||
|
**Input**: User-provided Spec 403 draft, current repo truth, Product Surface Contract, Spec 400 product-contract audit context, Spec 402 authorization proof close-out, and existing evidence/currentness runtime surfaces.
|
||||||
|
|
||||||
|
## Candidate Selection Context
|
||||||
|
|
||||||
|
- **Selected candidate**: Evidence Anchor & Currentness Runtime Closure.
|
||||||
|
- **Source location**: Direct user-provided Spec 403 draft in the 2026-06-23 request, promoted from Spec 400 evidence/currentness product-contract risk and Spec 402's recommended next action.
|
||||||
|
- **Why selected**: `docs/product/spec-candidates.md` currently reports no safe automatic next-best-prep target, but the operator supplied a direct manual candidate. Spec 402 now reports `PASS` with no P0/P1 authorization blockers and explicitly recommends proceeding to Spec 403 after review. This candidate closes the next trust gap before management-report PDF staging validation or broader customer-output claims.
|
||||||
|
- **Roadmap relationship**: Supports the current productization and governance-hardening lane by proving evidence-backed claims across existing review, report, evidence, OperationRun, baseline, restore, and customer-safe surfaces before expanding customer-output or management-report claims.
|
||||||
|
- **Close alternatives deferred**:
|
||||||
|
- Spec 404 - Management Report PDF Staging Validation is deferred until Spec 403 returns `PASS` or acceptable `PASS WITH CONDITIONS`.
|
||||||
|
- Governance artifact lifecycle/retention runtime remains broader P2 lifecycle work and must not be hidden inside evidence/currentness closure.
|
||||||
|
- JSON-to-JSONB migrations and index work remain data-layer hardening, not evidence-claim truth closure.
|
||||||
|
- Full browser/UX/runtime audit remains out of scope; this spec requires focused browser proof only.
|
||||||
|
- Provider readiness/onboarding productization remains optional productization work unless evidence/currentness proof exposes a P0/P1 product decision blocker.
|
||||||
|
- **Completed-spec guardrail result**:
|
||||||
|
- No `specs/403-evidence-anchor-currentness-runtime-closure/` package existed before this preparation.
|
||||||
|
- Specs 388 and 393 carry completed checklist/task signals and are read-only context for proof/currentness and evidence anchor behavior.
|
||||||
|
- Specs 400, 401, and 402 carry audit, implementation-report, validation, browser, or completed-task signals and are read-only historical context.
|
||||||
|
- Related specs such as 390 and 394 are implementation/preparation context only; do not rewrite, normalize, uncheck, or remove their historical task or validation language.
|
||||||
|
- This package must build on existing repo truth such as `EvidenceAnchorResolver`, `EvidenceAnchorResult`, `EvidenceSnapshotStatus`, `EvidenceCompletenessState`, Spec 388 proof-currentness semantics, and Spec 393 evidence anchor reconciliation rather than replacing them with a new framework.
|
||||||
|
- **Smallest viable implementation slice**: Inventory existing evidence/currentness claims; build an Evidence/Currentness Coverage Matrix; add targeted tests for stale, missing, failed, partial, released, internal-only, customer-safe, unauthorized, cross-workspace, cross-environment, and OperationRun proof states; apply only minimal runtime fixes for confirmed misleading or unsafe evidence/currentness behavior; record final results in an implementation report.
|
||||||
|
- **Feature description for Spec Kit**: Prove and minimally harden existing evidence-backed TenantPilot surfaces so admin, customer review, report, OperationRun, findings, baseline, and restore outputs distinguish current runtime evidence, stale/missing/failed/partial evidence, released review/report evidence, internal-only proof, and customer-safe proof without adding new surfaces, new product vocabulary, or a broad audit framework.
|
||||||
|
|
||||||
|
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
|
||||||
|
|
||||||
|
- **Problem**: Evidence-backed TenantPilot surfaces can imply stronger proof than the underlying evidence supports if current, stale, missing, failed, partial, released, internal-only, and customer-safe evidence states are not consistently represented at runtime.
|
||||||
|
- **Today's failure**: A future implementation can pass local tests while a customer-safe review, report receipt, evidence overview, OperationRun proof link, finding reference, baseline compare, or restore readiness surface overstates currentness, exposes internal proof, reuses stale proof, or confuses released evidence with live runtime evidence.
|
||||||
|
- **User-visible improvement**: Operators and customer reviewers see truthful evidence status, know whether proof is current or historical, and do not receive stronger claims than TenantPilot can prove for the active workspace and managed environment.
|
||||||
|
- **Smallest enterprise-capable version**: A matrix-first runtime closure over existing evidence/currentness surfaces, focused tests, focused browser proof, and minimal fixes for confirmed incorrect labels, links, scoping, or disclosure. No new route, navigation entry, customer output category, report runtime, provider model, persisted truth, or evidence taxonomy is included.
|
||||||
|
- **Explicit non-goals**: No management-report PDF staging validation, no new PDF/report template, no new customer review surface, no new evidence provider, no provider integration, no governance artifact lifecycle/retention/export/delete/hold work, no JSONB migration, no full browser/UX/runtime audit, no dashboard redesign, no new product vocabulary, no completed-spec rewrite.
|
||||||
|
- **Permanent complexity imported**: A spec-local implementation report and coverage matrix, focused feature/Filament/browser tests, and possibly small updates to existing helpers or labels. No new persisted table, source of truth, enum/status family, route family, product taxonomy, cross-domain UI framework, or broad proof registry is allowed by default.
|
||||||
|
- **Why now**: Spec 400 flagged evidence/currentness as a gating productization risk; Spec 401 closed selected high-risk action proof; Spec 402 now reports no P0/P1 authorization blockers and recommends Spec 403. This is the last bounded trust closure before management-report PDF staging validation.
|
||||||
|
- **Why not local**: A local label fix would not prove evidence overview, review packs, report receipts, customer review workspace, OperationRun proof, findings, baseline, restore, and customer/internal boundaries together. The work must be matrix-first so fixes stay targeted and evidence-backed.
|
||||||
|
- **Approval class**: Core Enterprise.
|
||||||
|
- **Red flags triggered**: Many surfaces and proof semantics. Defense: the matrix is a spec-local closure artifact, not runtime infrastructure; scope is existing behavior only; new vocabulary, new surfaces, and broad frameworks are forbidden.
|
||||||
|
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 11/12**
|
||||||
|
- **Decision**: approve as a bounded evidence/currentness runtime closure package.
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
|
||||||
|
TenantPilot already has evidence snapshots, evidence anchors, review packs, stored reports, OperationRun proof links, baseline/restore proof surfaces, and customer review outputs. The remaining risk is not the absence of evidence. The risk is inconsistent or over-strong runtime claims about what that evidence proves.
|
||||||
|
|
||||||
|
This spec answers:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Can TenantPilot prove that evidence-backed UI and customer-output surfaces distinguish current, stale, missing, failed, partial, released, internal-only, and customer-safe proof correctly?
|
||||||
|
```
|
||||||
|
|
||||||
|
A surface is closed only when the implementation and tests prove:
|
||||||
|
|
||||||
|
- which evidence is authoritative for the claim,
|
||||||
|
- whether that evidence is current, stale, missing, failed, partial, superseded, released, or historical,
|
||||||
|
- whether it belongs to the active workspace and managed environment,
|
||||||
|
- whether it is internal-only or customer-safe,
|
||||||
|
- whether released review/report evidence differs from current runtime evidence,
|
||||||
|
- whether missing proof is represented honestly,
|
||||||
|
- whether OperationRun proof links are authorized and scoped,
|
||||||
|
- and whether no customer-facing claim is stronger than the underlying evidence.
|
||||||
|
|
||||||
|
## Product / Business Value
|
||||||
|
|
||||||
|
TenantPilot is a governance/security-adjacent SaaS. Evidence-backed claims such as "ready", "current", "verified", "released", "complete", or "customer-safe" must be true at runtime. Closing this gap reduces false confidence, prevents customer-visible proof leakage, and gives reviewers a concrete proof matrix before management-report PDF staging validation or broader customer-output expansion.
|
||||||
|
|
||||||
|
## Primary Users / Operators
|
||||||
|
|
||||||
|
- Workspace owners, managers, and operators who use evidence/review/report/restore/baseline surfaces to make governance decisions.
|
||||||
|
- Customer reviewers consuming customer-safe review workspace and report outputs.
|
||||||
|
- Support/platform reviewers who may inspect internal proof through authorized secondary surfaces.
|
||||||
|
- Engineering reviewers and future implementation agents validating evidence/currentness truth.
|
||||||
|
|
||||||
|
## Spec Scope Fields *(mandatory)*
|
||||||
|
|
||||||
|
- **Scope**: Existing rendered and data-backed surfaces that display or depend on evidence/currentness, including evidence overview, evidence detail/anchors, review packs, customer review workspace, report receipt/output, OperationRun proof references, findings/governance references, baseline compare evidence, restore readiness/proof, and dashboard/readiness indicators where they summarize evidence state.
|
||||||
|
- **Primary Routes / Surfaces**:
|
||||||
|
- Evidence overview: `apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php`.
|
||||||
|
- Evidence detail/resource: `apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php` and nested pages.
|
||||||
|
- Customer review workspace: `apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php`.
|
||||||
|
- Environment reviews and publication resolution: `apps/platform/app/Filament/Resources/EnvironmentReviewResource.php` and `ResolveReviewPublication`.
|
||||||
|
- Review packs and review output: `apps/platform/app/Filament/Resources/ReviewPackResource.php`.
|
||||||
|
- Stored reports/report receipt/output: `apps/platform/app/Filament/Resources/StoredReportResource.php` and report output builders/controllers discovered during implementation.
|
||||||
|
- OperationRun proof helpers and links: `OperationRunLinks`, `OperationRunPolicy`, Monitoring/Operations detail surfaces, and linked proof surfaces.
|
||||||
|
- Findings/governance references: finding and exception resources/pages where evidence is referenced.
|
||||||
|
- Baseline compare/proof surfaces: `BaselineCompareMatrix`, baseline snapshot/profile resources, and baseline evidence providers.
|
||||||
|
- Restore readiness/proof surfaces: `RestoreRunResource`, restore presenters, restore proof views, and restore readiness support classes.
|
||||||
|
- **Data Ownership**:
|
||||||
|
- Evidence snapshots and evidence items remain workspace plus managed-environment owned.
|
||||||
|
- Review packs, environment reviews, stored reports, findings, restore runs, baseline snapshots, and OperationRuns keep their existing ownership and source-of-truth semantics.
|
||||||
|
- No new persisted entity/table/artifact is approved by default.
|
||||||
|
- **RBAC**:
|
||||||
|
- Admin plane `/admin` uses authenticated `User`, workspace context, managed-environment entitlement, and existing capabilities/policies.
|
||||||
|
- Customer-safe surfaces may show only customer-safe evidence summaries and must not expose raw internal proof by default.
|
||||||
|
- OperationRun and evidence technical links must be authorized and scoped before rendering or serving.
|
||||||
|
- Non-member or wrong workspace/environment remains deny-as-not-found; member missing capability remains forbidden at execution/access-check level.
|
||||||
|
|
||||||
|
For canonical-view or mixed-scope specs:
|
||||||
|
|
||||||
|
- **Default filter behavior when environment-context is active**: Existing explicit `environment_id` filters remain page-owned and must not silently choose arbitrary current evidence for a workspace-wide view.
|
||||||
|
- **Explicit entitlement checks preventing cross-tenant leakage**: All evidence, OperationRun proof, review/report, baseline, restore, and finding references must prove workspace and managed-environment entitlement before rendering or linking records.
|
||||||
|
|
||||||
|
## No Legacy / No Backward Compatibility Constraint *(mandatory)*
|
||||||
|
|
||||||
|
TenantPilot is pre-production for this product-contract closure.
|
||||||
|
|
||||||
|
- **Compatibility posture**: canonical current evidence/currentness behavior over old ambiguous labels or fallback selectors.
|
||||||
|
- **Legacy aliases, fallback readers, hidden routes, duplicate UI, old labels, or historical fixtures kept?**: no new compatibility path is allowed. Existing historical specs remain read-only and are not normalized.
|
||||||
|
- **Why clean replacement is safe now**: Evidence/currentness claims are trust-critical. If a label or fallback overstates proof, it must be corrected or explicitly classified as product-decision debt rather than preserved for compatibility.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- [ ] 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
|
||||||
|
|
||||||
|
Rationale: Spec 403 may alter existing labels, links, disabled/hidden proof paths, and customer-safe evidence summaries on existing pages only. It must not add new pages, routes, navigation, or actions unless the spec/plan are updated first.
|
||||||
|
|
||||||
|
## UI/Productization Coverage *(mandatory when UI Surface Impact is not "No UI surface impact")*
|
||||||
|
|
||||||
|
- **Route/page/surface**: Existing evidence overview/detail, customer review workspace, environment review detail/publication resolution, review pack detail/output, stored report detail/output, operations proof links, finding references, baseline compare, and restore readiness/proof surfaces.
|
||||||
|
- **Current or new page archetype**: Existing Receipt, Decision, Report, Search/Index, Technical Annex, Dashboard, and Wizard archetypes only. No new archetype may be introduced.
|
||||||
|
- **Design depth**: Domain Pattern Surface / Technical Annex / customer-safe Report or Review Surface depending on existing page. This spec is runtime truth closure, not redesign.
|
||||||
|
- **Repo-truth level**: repo-verified existing surfaces.
|
||||||
|
- **Existing pattern reused**: `EvidenceAnchorResolver`, `EvidenceAnchorResult`, `EvidenceSnapshotStatus`, `EvidenceCompletenessState`, Spec 388 proof-currentness helpers, existing policies, existing Product Surface Contract patterns, existing badges/shared presenters where already used.
|
||||||
|
- **New pattern required**: none by default. If implementation requires a new helper, first prove why extending existing evidence/proof paths is insufficient and update spec/plan with proportionality review.
|
||||||
|
- **Screenshot required**: no full screenshot set by default. Focused browser proof is required for critical evidence/currentness paths.
|
||||||
|
- **Page audit required**: no full page audit. Matrix and focused proof only.
|
||||||
|
- **Customer-safe review required**: yes for Customer Review Workspace, review pack/report output, and any customer-visible evidence/proof labels.
|
||||||
|
- **Dangerous-action review required**: no new dangerous action is in scope. Restore/high-impact existing actions are inspected only where proof/currentness labels may overstate readiness.
|
||||||
|
- **Coverage files updated or explicitly not needed**:
|
||||||
|
- [x] `docs/ui-ux-enterprise-audit/route-inventory.md` - review/update touched existing surfaces if runtime UI files or reachable evidence/status semantics change.
|
||||||
|
- [x] `docs/ui-ux-enterprise-audit/design-coverage-matrix.md` - review/update touched existing surfaces if runtime UI files or reachable evidence/status semantics change.
|
||||||
|
- [ ] `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 new reachable UI surface; existing evidence/currentness presentation hardening only`
|
||||||
|
- **No-impact rationale when applicable**: N/A because existing rendered evidence/status presentation may change.
|
||||||
|
|
||||||
|
## Product Surface Impact *(mandatory for UI-affecting specs)*
|
||||||
|
|
||||||
|
Reference: `docs/product/standards/product-surface-contract.md`.
|
||||||
|
|
||||||
|
- **Product Surface Contract applies?**: yes. Evidence, reports, customer output, readiness/proof, OperationRun links, and status presentation are explicitly covered by the contract.
|
||||||
|
- **Page archetype**: existing archetypes only. Exact affected pages and archetypes are recorded in the implementation report.
|
||||||
|
- **Primary user question**: "Can I trust this evidence-backed claim for this workspace/environment and audience?"
|
||||||
|
- **Primary action**: no new primary action. Existing next actions must remain accurate and must not compete with technical proof links.
|
||||||
|
- **Surface budget result**: neutral by default. No new tables, top-level readiness model, raw technical links, or OperationRun links may be added to default product-facing views.
|
||||||
|
- **Technical Annex / deep-link demotion**: OperationRun, raw evidence IDs, source keys, detectors, fingerprints, raw provider payloads, and technical logs remain hidden, secondary, or authorized internal/audit links only.
|
||||||
|
- **Canonical status vocabulary**: touched Evidence Overview runtime proof uses `Ready`, `Needs attention`, `Blocked`, `Running`, `Failed`, `Expired`, `Not configured`, and `Historical`. Broader Product Surface Contract vocabulary still permits `Unknown` and `Superseded`, but this runtime closure must not introduce them for current Evidence Overview proof states.
|
||||||
|
- **Visible complexity impact**: neutral or decreased. Corrections should make proof meaning clearer without adding a new UI layer.
|
||||||
|
- **Product Surface exceptions**: none planned.
|
||||||
|
|
||||||
|
## Browser Verification Plan *(mandatory)*
|
||||||
|
|
||||||
|
- **Browser proof required?**: yes.
|
||||||
|
- **No-browser rationale**: N/A.
|
||||||
|
- **Focused path when required**:
|
||||||
|
1. Admin Evidence Overview current/stale/missing/failed or partial path.
|
||||||
|
2. Evidence detail/anchor path for current versus stale/superseded/missing proof.
|
||||||
|
3. Customer Review Workspace or review/report output path proving customer-safe released proof.
|
||||||
|
4. Released review/report evidence path proving released evidence is not claimed as live current runtime evidence.
|
||||||
|
5. Unauthorized or cross-workspace/cross-environment evidence anchor path.
|
||||||
|
6. OperationRun proof link visibility or blocked access path.
|
||||||
|
- **Primary interaction to execute**: navigate existing pages, verify labels/links/actions, test denied URLs, inspect customer-safe output, and record console/runtime/Livewire/Filament errors.
|
||||||
|
- **Console, Livewire, Filament, network, and 500-error checks**: planned.
|
||||||
|
- **Full-suite failure triage**: unrelated browser failures are documented separately; do not claim full browser audit.
|
||||||
|
|
||||||
|
## Human Product Sanity Check *(mandatory)*
|
||||||
|
|
||||||
|
- **Required?**: yes.
|
||||||
|
- **No-human-sanity rationale**: N/A.
|
||||||
|
- **Reviewer questions**: Does each touched surface immediately communicate what proof exists? Is current versus released versus stale clear? Are technical details demoted? Are customer-safe boundaries credible? Is there exactly one dominant next action? Did visible complexity stay neutral or decrease?
|
||||||
|
- **Planned result location**: `specs/403-evidence-anchor-currentness-runtime-closure/implementation-report.md`.
|
||||||
|
|
||||||
|
## Product Surface Merge Gate Checklist *(mandatory)*
|
||||||
|
|
||||||
|
- [x] No-legacy posture or approved exception recorded.
|
||||||
|
- [x] Product Surface Impact is completed for existing evidence/currentness hardening.
|
||||||
|
- [x] UI coverage registry review/update is required for touched existing surfaces when runtime UI files or reachable evidence/status semantics change.
|
||||||
|
- [x] Browser proof is required for representative rendered evidence/currentness paths.
|
||||||
|
- [x] Human Product Sanity is required for changed rendered behavior.
|
||||||
|
- [x] Product Surface exceptions are documented as `none planned`.
|
||||||
|
- [x] Implementation report will state Livewire v4 compliance, provider registration location, global search posture, destructive/high-impact action posture, asset strategy, tests/browser result, deployment impact, visible complexity outcome, and no completed-spec rewrite assertion.
|
||||||
|
|
||||||
|
## Cross-Cutting / Shared Pattern Reuse
|
||||||
|
|
||||||
|
- **Cross-cutting feature?**: yes.
|
||||||
|
- **Interaction class(es)**: evidence/report viewers, status messaging, action links, OperationRun proof links, customer-safe output, readiness/proof labels.
|
||||||
|
- **Systems touched**: `EvidenceAnchorResolver`, `EvidenceAnchorResult`, evidence snapshot resources, environment review/review pack/stored report resources, customer review workspace, OperationRun links/policies, baseline and restore proof surfaces, existing badges/presenters where used.
|
||||||
|
- **Existing pattern(s) to extend**: existing evidence anchor and proof-currentness paths from Specs 388 and 393, existing badge/shared presenter paths, existing policies and scoped queries.
|
||||||
|
- **Shared contract / presenter / builder / renderer to reuse**: `EvidenceAnchorResolver`, `EvidenceAnchorResult`, `EvidenceSnapshotStatus`, `EvidenceCompletenessState`, `ResolutionProof*` support where review-publication proof is involved, `OperationRunLinks`, `OperationRunPolicy`, and existing ArtifactTruth/Badge paths where already present.
|
||||||
|
- **Why the existing shared path is sufficient or insufficient**: sufficient until the coverage matrix proves a concrete gap. New runtime abstractions are out of scope by default.
|
||||||
|
- **Allowed deviation and why**: bounded local label/link correction only when existing shared path cannot express a false claim without broader changes. Any recurring structural gap becomes a follow-up spec.
|
||||||
|
- **Consistency impact**: default product surfaces must agree on current, stale, missing, failed, partial, released, internal-only, customer-safe, and unauthorized proof semantics.
|
||||||
|
- **Review focus**: no parallel proof vocabulary, no raw technical leakage, no arbitrary latest evidence fallback, no customer-facing overclaim, no completed-spec rewrite.
|
||||||
|
|
||||||
|
## OperationRun UX Impact
|
||||||
|
|
||||||
|
- **Touches OperationRun start/completion/link UX?**: link/proof visibility may be touched; no new OperationRun start/completion behavior is planned.
|
||||||
|
- **Shared OperationRun UX contract/layer reused**: existing `OperationRunLinks`, `OperationRunPolicy`, OperationRun Start UX Contract, and Monitoring -> Operations detail route helpers.
|
||||||
|
- **Delegated start/completion UX behaviors**: unchanged.
|
||||||
|
- **Local surface-owned behavior that remains**: existing proof/reference display and existing initiation inputs only.
|
||||||
|
- **Queued DB-notification policy**: unchanged; no new queued DB notifications.
|
||||||
|
- **Terminal notification path**: unchanged.
|
||||||
|
- **Exception required?**: none planned.
|
||||||
|
|
||||||
|
## Provider Boundary / Platform Core Check
|
||||||
|
|
||||||
|
- **Shared provider/platform boundary touched?**: possibly, only where provider freshness affects evidence/currentness claims.
|
||||||
|
- **Boundary classification**: mixed. Provider freshness/permissions remain provider-owned; evidence, review/report truth, OperationRun proof, workspace/environment scoping, and customer-safe disclosure are platform-core product semantics.
|
||||||
|
- **Seams affected**: evidence currentness, provider freshness labels that influence evidence claims, report/review output, restore/baseline proof, OperationRun proof links.
|
||||||
|
- **Neutral platform terms preserved or introduced**: workspace, managed environment, provider, evidence, proof, review, report, operation, current, released, historical, customer-safe.
|
||||||
|
- **Provider-specific semantics retained and why**: Microsoft/Graph/Entra terms remain only in existing provider-owned diagnostics and are not promoted to platform-core evidence vocabulary.
|
||||||
|
- **Why this does not deepen provider coupling accidentally**: the spec verifies existing evidence/currentness claims and forbids new provider models or taxonomies.
|
||||||
|
- **Follow-up path**: provider readiness/onboarding productization remains a separate manual candidate if runtime evidence shows operator friction.
|
||||||
|
|
||||||
|
## UI / Surface Guardrail Impact
|
||||||
|
|
||||||
|
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| Evidence overview/detail | yes, if labels or links change | Native Filament + existing evidence helpers | evidence/status/link | page, table, detail route | none planned | Existing-surface evidence truth only |
|
||||||
|
| Customer review workspace | yes | Existing Filament page | customer-safe evidence/report output | page, table, report/action link | none planned | No raw proof by default |
|
||||||
|
| Review pack/environment review/stored report | yes | Native Filament resources/pages | released/current proof and report output | detail, actions, report links | none planned | Existing output semantics only |
|
||||||
|
| OperationRun proof links | possibly | Existing link helpers/policies | technical proof links | link/action/detail route | none planned | Link visibility/scoping only |
|
||||||
|
| Baseline/restore/finding references | possibly | Existing resources/pages/views | evidence/readiness/proof | page, detail, wizard, table | none planned | Correct labels only |
|
||||||
|
|
||||||
|
## Decision-First Surface Role
|
||||||
|
|
||||||
|
| 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 | Dashboard Page | Decide whether environment evidence can support review/report work | environment, evidence state, generated time, next step | internal evidence detail and technical proof links | Dashboard because it summarizes workspace evidence readiness; proof inspection stays secondary through demoted links | evidence readiness and review preparation | reduces search for stale/missing proof |
|
||||||
|
| Customer Review Workspace | Primary Decision Surface for customer-safe consumption | Decide whether released review/output can be consumed or downloaded | released review state, customer-safe evidence summary, package/download availability | internal evidence hidden; detail only through authorized operator paths | Primary for customer-safe consumption | customer review/report consumption | prevents customer-facing overclaim |
|
||||||
|
| Review Pack / Report Receipt | Receipt Page | Decide whether an output happened and can be trusted | generated/released state, evidence basis, customer-safe availability | technical proof and operation detail secondary | Receipt because it proves what happened | output verification | separates current runtime from released evidence |
|
||||||
|
| OperationRun detail/link | Technical Annex Page | Diagnose proof or execution outcome | run status and authorized link only when relevant | full operation detail in Monitoring | Not primary for customer/customer default views | troubleshooting/audit | keeps technical proof demoted |
|
||||||
|
| Restore/Baseline/Finding evidence references | Decision or Wizard context depending on existing surface | Decide whether readiness/result/governance claim is trustworthy | currentness/readiness/evidence availability | raw proof secondary | Existing primary context remains unchanged | restore/baseline/finding workflow | prevents false readiness |
|
||||||
|
|
||||||
|
## Audience-Aware Disclosure
|
||||||
|
|
||||||
|
| 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 |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| Customer Review Workspace | customer/read-only, operator-MSP | released review status, customer-safe evidence summary, package/download state | operator-only review/report detail via existing authorized paths | raw evidence, OperationRun, fingerprints, source keys hidden | open/download customer-safe output when ready | raw evidence and technical proof | customer summary states evidence basis once |
|
||||||
|
| Evidence Overview / Evidence Detail | operator-MSP, support-platform | evidence state, currentness, next step | technical detail and source summary | raw payload/source keys only where existing authorized detail allows | inspect current evidence or refresh/generate evidence | cross-environment records, raw payloads | anchor result owns link meaning |
|
||||||
|
| Review Pack / Stored Report | operator-MSP, customer-safe output | released/generated evidence basis and customer-safe output state | report/review internals where authorized | raw payloads and renderer internals hidden | open/download output or inspect review | OperationRun/raw artifact links in default customer view | released versus current labels do not repeat conflicting truth |
|
||||||
|
| Restore/Baseline/Finding references | operator-MSP | readiness/result/evidence availability | existing proof/diagnostic sections | raw restore/baseline/evidence payloads hidden | continue existing workflow action | technical proof details secondary | one readiness/currentness source per concept |
|
||||||
|
|
||||||
|
## UI/UX Surface Classification
|
||||||
|
|
||||||
|
| 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 | List / Table / Read-only | Monitoring registry | Inspect current evidence or see missing/stale proof | existing row navigation/filter | existing | existing secondary/internal links | none | `/admin/evidence` route name in repo | evidence snapshot detail when linkable | workspace and explicit environment filter | Evidence | evidence state and next step | none |
|
||||||
|
| Customer Review Workspace | Workbench / Report | Customer-safe review hub | Open or download customer-safe output | existing row/action | existing | existing secondary actions | none | existing customer review workspace route | review/report/pack detail by existing routes | workspace and environment filter | Customer review | released/customer-safe proof basis | none |
|
||||||
|
| Review Pack / Stored Report | Receipt / Detail | Output receipt | Open/download/inspect output | existing detail pages | existing | internal proof secondary | existing actions unchanged | existing resources | existing resource view pages | workspace/environment | Review pack / Stored report | generated/released evidence basis | none |
|
||||||
|
| Restore/Baseline/Finding references | Existing Decision/Wizard/List | Existing domain surfaces | Continue safe workflow or inspect proof | existing | existing | existing proof/detail secondary | existing destructive actions unchanged | existing resources/pages | existing resources/pages | workspace/environment | Existing domain nouns | readiness/currentness truth | none |
|
||||||
|
|
||||||
|
## Operator Surface Contract
|
||||||
|
|
||||||
|
| 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 operator | Decide whether evidence can support review/report work | Secondary context list | Is the evidence basis current and usable? | state, generated time, next step, scoped link if authorized | raw items, source keys, technical detail | evidence currentness, completeness, lifecycle | read-only | inspect evidence / clear filter | none |
|
||||||
|
| Customer Review Workspace | Customer reviewer / MSP operator | Decide whether released output is safe to consume | Customer-safe report hub | Can this released review/output be trusted? | released review/output state, customer-safe summary, download state | raw evidence, operations, fingerprints | released/current distinction, customer-safe availability | read-only/customer output | open/download customer-safe output | none |
|
||||||
|
| OperationRun proof links | Operator/support | Diagnose proof/execution | Technical annex link | Can I inspect the operation proof? | scoped authorized link only when allowed | full run context | execution outcome | read-only detail | view run | none |
|
||||||
|
| Restore/Baseline/Finding references | Tenant operator | Decide whether readiness/result/governance claim is safe | Existing domain surface | Does this claim have current proof? | readiness/evidence state and next safe action | raw payloads/proof internals | readiness, evidence, execution result | existing behavior | existing primary action | existing dangerous actions unchanged |
|
||||||
|
|
||||||
|
## UI Action Matrix *(mandatory when Filament is changed)*
|
||||||
|
|
||||||
|
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| Evidence Overview | `EvidenceOverview` page | existing clear filters only | existing row/detail link only when authorized/current enough | none planned | none | existing | N/A | N/A | no mutation | Correct labels/links only |
|
||||||
|
| EvidenceSnapshotResource | resource pages | existing actions only | existing record URL | existing | existing | existing | existing | existing | existing for mutations | No new actions |
|
||||||
|
| CustomerReviewWorkspace | page | existing customer-safe actions | existing row/action links | existing | none | existing | N/A | N/A | existing acknowledgements only | No raw proof links by default |
|
||||||
|
| EnvironmentReview/ReviewPack/StoredReport resources | resource pages | existing actions only | existing | existing | existing | existing | existing | existing | existing | Released/current labels and proof links only |
|
||||||
|
| Restore/Baseline/Finding resources/pages | existing pages | existing actions only | existing | existing | existing | existing | existing | existing | existing | No new dangerous/high-impact actions |
|
||||||
|
|
||||||
|
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||||
|
|
||||||
|
- **New source of truth?**: no.
|
||||||
|
- **New persisted entity/table/artifact?**: no.
|
||||||
|
- **New abstraction?**: no by default. Extending existing helpers may be allowed only if a matrix finding proves direct local correction is insufficient.
|
||||||
|
- **New enum/state/reason family?**: no new family. Existing statuses and canonical product vocabulary must be reused or mapped.
|
||||||
|
- **New cross-domain UI framework/taxonomy?**: no.
|
||||||
|
- **Current operator problem**: existing surfaces may overstate evidence proof or currentness, especially customer-safe/released/current distinctions.
|
||||||
|
- **Existing structure is insufficient because**: unknown until matrix/test inventory. If existing structures can represent the correct truth, use them. If not, update spec/plan before adding structure.
|
||||||
|
- **Narrowest correct implementation**: matrix-first proof, targeted tests, local/shared-path corrections, no broad abstraction.
|
||||||
|
- **Ownership cost**: focused tests, implementation report, and any small helper extension. No new runtime ownership surface by default.
|
||||||
|
- **Alternative intentionally rejected**: broad proof-currentness framework, new evidence taxonomy, new UI status model, new report runtime, and full browser audit.
|
||||||
|
- **Release truth**: current-release trust closure.
|
||||||
|
|
||||||
|
### Compatibility posture
|
||||||
|
|
||||||
|
Pre-production canonical correction. No compatibility aliases or legacy fallback behavior are approved when they preserve false evidence/currentness claims.
|
||||||
|
|
||||||
|
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
||||||
|
|
||||||
|
- **Test purpose / classification**: Unit for existing resolver/helper semantics; Feature/Filament for rendered resource/page state and RBAC; Browser for focused evidence/currentness/customer-safe flows; Heavy-Governance only if an explicit matrix guard is added.
|
||||||
|
- **Validation lane(s)**: confidence, browser, optional heavy-governance.
|
||||||
|
- **Why this classification and these lanes are sufficient**: evidence/currentness closure requires both business truth tests and representative rendered proof. Full browser audit is out of scope.
|
||||||
|
- **New or expanded test families**: focused Spec 403 tests only; reuse existing Spec 388/393/Evidence/Review/Report/Restore/Baseline tests where possible.
|
||||||
|
- **Fixture / helper cost impact**: workspace, managed-environment, user, evidence snapshot, review, report, review pack, OperationRun, restore, baseline fixtures. Keep new fixture breadth explicit and opt-in.
|
||||||
|
- **Heavy-family visibility / justification**: none planned.
|
||||||
|
- **Special surface test profile**: shared-detail-family, customer-safe report surface, monitoring-state-page, restore wizard/detail, baseline decision surface.
|
||||||
|
- **Standard-native relief or required special coverage**: standard-native Filament relief for unchanged CRUD/resource mechanics; special browser proof for customer-safe and evidence/currentness paths.
|
||||||
|
- **Reviewer handoff**: verify lane fit, no hidden broad browser/full-suite claim, no new helper default breadth, no raw evidence leakage.
|
||||||
|
- **Budget / baseline / trend impact**: none expected; document if focused browser/fixture setup materially increases runtime.
|
||||||
|
- **Escalation needed**: document-in-feature for bounded implementation decisions; follow-up-spec for structural product decisions or recurring proof gaps.
|
||||||
|
- **Active feature PR close-out entry**: `Guardrail / Exception / Smoke Coverage`.
|
||||||
|
- **Planned validation commands**:
|
||||||
|
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Spec403`
|
||||||
|
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Evidence/EvidenceOverviewPageTest.php tests/Feature/Evidence/EvidenceSnapshotResourceTest.php --compact`
|
||||||
|
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/EnvironmentReview/Spec388ReviewPublicationProofCurrentnessTest.php --compact`
|
||||||
|
- `cd apps/platform && ./vendor/bin/sail artisan test tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php --compact`
|
||||||
|
- targeted restore/baseline/finding/report tests changed by implementation
|
||||||
|
- focused browser smoke for Spec 403
|
||||||
|
- `git diff --check`
|
||||||
|
- project formatter for changed PHP files
|
||||||
|
|
||||||
|
## User Scenarios & Testing *(mandatory)*
|
||||||
|
|
||||||
|
### User Story 1 - Current Evidence Claims Are Honest (Priority: P1)
|
||||||
|
|
||||||
|
As a workspace operator, I need current evidence links and labels to use only scoped, complete, non-expired evidence so I do not rely on stale, partial, failed, missing, or wrong-environment proof.
|
||||||
|
|
||||||
|
**Why this priority**: Current evidence is the basis for review, report, baseline, and restore decisions.
|
||||||
|
|
||||||
|
**Independent Test**: Seed current, stale, partial, failed, missing, superseded, wrong-workspace, and wrong-environment evidence and verify only valid scoped current evidence can support a current evidence claim or link.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** an environment has complete active non-expired evidence with no missing or stale dimensions, **When** the current evidence surface renders, **Then** the claim may be shown as current and linkable only for an authorized actor.
|
||||||
|
2. **Given** only stale, expired, partial, failed, queued, generating, superseded, or wrong-scope evidence exists, **When** the current evidence surface renders, **Then** it shows `Needs attention`, `Running`, `Failed`, `Blocked`, `Expired`, or `Not configured` language and does not expose a current proof link.
|
||||||
|
3. **Given** a user is not entitled to a workspace or environment, **When** they access an evidence anchor URL or proof link, **Then** access is denied as not found where membership/scope is missing.
|
||||||
|
|
||||||
|
### User Story 2 - Released And Customer-Safe Proof Is Distinct (Priority: P1)
|
||||||
|
|
||||||
|
As a customer reviewer or MSP operator, I need released review/report evidence to be distinguished from current runtime evidence and stripped of internal proof so customer output does not overclaim live/current state or leak internal detail.
|
||||||
|
|
||||||
|
**Why this priority**: Customer-facing proof must be trustworthy before management-report PDF staging validation or broader output claims.
|
||||||
|
|
||||||
|
**Independent Test**: Publish/release a review/report with bound evidence, create newer runtime evidence, and verify customer-safe output remains release-bound and customer-safe while operator surfaces can still compare to current runtime evidence where authorized.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** a released review or report was generated from evidence A and newer runtime evidence B exists, **When** the customer-safe output renders, **Then** it identifies the released/generated evidence basis and does not claim live current runtime state.
|
||||||
|
2. **Given** released evidence is missing, failed, partial, stale, or not customer-safe, **When** customer-safe output renders, **Then** it states the limitation or unavailability and does not expose internal proof as a substitute.
|
||||||
|
3. **Given** an internal proof link exists, **When** a customer-safe context renders, **Then** raw evidence routes, OperationRun links, source keys, fingerprints, provider payloads, and internal reason families are hidden.
|
||||||
|
|
||||||
|
### User Story 3 - Operation, Restore, Baseline, Finding, And Report Proof Links Are Scoped (Priority: P2)
|
||||||
|
|
||||||
|
As an operator, I need secondary proof links to be authorized, scoped, and labelled with the right proof meaning so I do not mistake execution proof, restore readiness, baseline comparison, finding evidence, or report receipt proof for stronger current evidence than it is.
|
||||||
|
|
||||||
|
**Why this priority**: These surfaces are not all customer-default surfaces, but they can still drive unsafe decisions if proof labels are wrong.
|
||||||
|
|
||||||
|
**Independent Test**: Use existing OperationRun, restore, baseline, finding, and report fixtures to verify proof links and labels are scoped, authorized, and do not overstate currentness or success.
|
||||||
|
|
||||||
|
**Acceptance Scenarios**:
|
||||||
|
|
||||||
|
1. **Given** an OperationRun failed, was cancelled, is running, or belongs to another workspace/environment, **When** a proof surface renders, **Then** it is not shown as successful current evidence and links are hidden or denied as appropriate.
|
||||||
|
2. **Given** restore readiness or baseline compare proof is stale, missing, failed, partial, or expired, **When** the surface renders, **Then** readiness/result language does not imply current executable proof.
|
||||||
|
3. **Given** a finding or governance reference cites evidence, **When** the reference renders, **Then** the surface distinguishes current, released/historical, missing, blocked, or needs-attention proof where applicable.
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
|
||||||
|
- Workspace-wide evidence overview must not choose one arbitrary managed-environment snapshot as current evidence.
|
||||||
|
- Evidence generated for one managed environment must not support another managed environment's claim even inside the same workspace.
|
||||||
|
- A released review/report remains release-bound when current runtime evidence changes.
|
||||||
|
- Successful OperationRun execution is not evidence of artifact/currentness unless the artifact proof exists and is current for the claim.
|
||||||
|
- Failed, cancelled, blocked, running, or stale OperationRuns do not create success proof.
|
||||||
|
- Missing product decisions must be reported as blockers or deferrals, not silently invented.
|
||||||
|
|
||||||
|
## Requirements *(mandatory)*
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
- **FR-403-001**: The implementation MUST create an Evidence/Currentness Coverage Matrix in `implementation-report.md` before runtime fixes are classified as complete.
|
||||||
|
- **FR-403-002**: Current evidence claims MUST be backed only by evidence that is scoped to the active workspace and managed environment, complete, active/current according to existing repo contracts, non-expired, and authorized for the actor.
|
||||||
|
- **FR-403-003**: As an umbrella prohibition, stale, expired, superseded, failed, queued, generating, partial, missing, wrong-workspace, wrong-environment, or unauthorized evidence MUST NOT be shown as current proof.
|
||||||
|
- **FR-403-004**: Customer-safe surfaces MUST show customer-safe summaries only and MUST NOT expose raw provider payloads, raw evidence source keys, fingerprints, raw evidence IDs, OperationRun URLs, stack traces, raw exception messages, internal reason ownership, or internal-only audit metadata by default.
|
||||||
|
- **FR-403-005**: Released review/report evidence MUST be distinguished from current runtime evidence and MUST remain tied to the evidence/report/review state used at release or generation time.
|
||||||
|
- **FR-403-006**: Missing evidence MUST be represented as `Not configured` or `Needs attention` and MUST NOT be shown as verified, ready, current, or complete.
|
||||||
|
- **FR-403-007**: Failed evidence collection or failed proof generation MUST be represented as `Failed`, `Blocked`, or `Needs attention` and MUST NOT be shown as successful proof.
|
||||||
|
- **FR-403-008**: Partial evidence MUST be represented as partial/needs attention/incomplete according to existing vocabulary and MUST NOT be shown as complete.
|
||||||
|
- **FR-403-009**: OperationRun proof links MUST be authorized, workspace-scoped, managed-environment-scoped where applicable, and hidden or denied when the actor lacks entitlement.
|
||||||
|
- **FR-403-010**: OperationRun proof MUST NOT be exposed in customer-safe context unless an existing contract explicitly allows the customer-safe form; technical OperationRun links remain secondary/internal.
|
||||||
|
- **FR-403-011**: Provider freshness or permission-limited provider state MUST affect evidence/currentness claims where existing repo contracts already connect provider freshness to evidence quality.
|
||||||
|
- **FR-403-012**: Baseline compare and restore readiness/proof surfaces MUST not treat stale, expired, failed, partial, or missing proof as current readiness.
|
||||||
|
- **FR-403-013**: Finding/governance references that cite evidence MUST not imply currentness when only released/historical/old proof exists.
|
||||||
|
- **FR-403-014**: Runtime fixes MUST reuse or minimally extend existing evidence/currentness helpers and shared patterns; adding a new persisted source of truth, enum/status family, product taxonomy, route, or broad resolver framework requires spec/plan update before coding.
|
||||||
|
- **FR-403-015**: Remaining P0/P1 findings MUST be fixed if safe and bounded, or reported with exact reason and blocking/defer classification if unresolved product decisions prevent a fix.
|
||||||
|
- **FR-403-016**: The final implementation report MUST include the Evidence/Currentness Coverage Matrix, runtime changes, tests, browser proof, customer-safe proof, current-versus-released proof, remaining findings, deferred items, validation commands, and recommended next action.
|
||||||
|
- **FR-403-017**: If implementation changes runtime UI files or reachable evidence/status semantics, it MUST review and update applicable `docs/ui-ux-enterprise-audit/route-inventory.md` and `docs/ui-ux-enterprise-audit/design-coverage-matrix.md` entries for touched existing surfaces, or record that existing registry entries were reviewed and remain current.
|
||||||
|
|
||||||
|
### Non-Functional Requirements
|
||||||
|
|
||||||
|
- **NFR-403-001**: Evidence/currentness evaluation for rendered pages MUST remain DB-only and MUST NOT make Graph/provider calls during UI render.
|
||||||
|
- **NFR-403-002**: Any new or changed tests MUST use the narrowest honest lane and keep expensive fixtures explicit.
|
||||||
|
- **NFR-403-003**: No secrets, tokens, raw provider payloads, raw credential payloads, or sensitive raw evidence dumps may appear in logs, tests, reports, screenshots, or implementation notes.
|
||||||
|
- **NFR-403-004**: Implementation MUST preserve Filament v5 and Livewire v4 compliance and avoid Filament v3/v4 or Livewire v3 APIs.
|
||||||
|
- **NFR-403-005**: Implementation MUST not add migrations, env vars, queues, scheduler changes, storage topology changes, panel provider changes, or asset registration unless spec/plan are updated first.
|
||||||
|
|
||||||
|
## Key Existing Truth Sources
|
||||||
|
|
||||||
|
- **Evidence truth**: `EvidenceSnapshot`, `EvidenceSnapshotItem`, `EvidenceSnapshotStatus`, `EvidenceCompletenessState`, `EvidenceAnchorResolver`, `EvidenceAnchorResult`.
|
||||||
|
- **Released review/report truth**: `EnvironmentReview`, `ReviewPack`, `StoredReport`, review pack service/builders, report output/receipt builders.
|
||||||
|
- **Execution proof truth**: `OperationRun`, `OperationRunService`, `OperationRunLinks`, `OperationRunPolicy`, Spec 388 proof-currentness support for review publication.
|
||||||
|
- **Readiness/proof domain truth**: restore safety/readiness support, baseline compare/evidence providers, finding/evidence references.
|
||||||
|
- **Authorization truth**: policies, capability resolvers, workspace/environment scoping, Spec 402 resource authorization proof.
|
||||||
|
|
||||||
|
## Success Criteria *(mandatory)*
|
||||||
|
|
||||||
|
### Measurable Outcomes
|
||||||
|
|
||||||
|
- **SC-403-001**: The final Evidence/Currentness Coverage Matrix covers all required surface groups from this spec or records why a group is not repo-real/applicable.
|
||||||
|
- **SC-403-002**: Targeted tests prove stale, missing, failed, partial, released/current, customer-safe, unauthorized, cross-workspace/cross-environment, and OperationRun proof behavior for the critical paths touched or confirmed.
|
||||||
|
- **SC-403-003**: Focused browser proof covers at least one admin evidence/currentness path, one customer-safe review/report path, one released evidence path, one stale/missing/failed path, one unauthorized/cross-scope path, and one OperationRun proof link visibility or blocked-access path, unless the implementation report records an exact no-browser blocker.
|
||||||
|
- **SC-403-004**: No P0 findings remain. If any P1 remains, each is explicitly classified with safe deferral or product-decision blocker language.
|
||||||
|
- **SC-403-005**: No customer-facing claim exceeds the underlying evidence, and no new product/currentness vocabulary is introduced.
|
||||||
|
- **SC-403-006**: The implementation report can recommend Spec 404 only when Spec 403 returns `PASS`, or can recommend bounded remediation when it returns `PASS WITH CONDITIONS` or `FAIL`.
|
||||||
|
|
||||||
|
## Required Evidence/Currentness Matrix
|
||||||
|
|
||||||
|
The implementation report MUST include this matrix shape:
|
||||||
|
|
||||||
|
| Surface | Evidence Source | Currentness Source | Released Snapshot Source | Customer-safe Boundary | Internal-only Risk | Workspace/Environment Scope | Authorization Mechanism | Test Proof | Browser Proof | Status | Risk | Follow-up |
|
||||||
|
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
| TBD during implementation | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | TBD | PASS / PASS WITH EXCEPTION / MISSING PROOF / DEFECT FOUND / PRODUCT DECISION REQUIRED / DEFERRED | P0/P1/P2/P3/None | TBD |
|
||||||
|
|
||||||
|
## Finding Severity
|
||||||
|
|
||||||
|
- **P0**: Customer-safe output exposes internal-only evidence; stale/failed/missing proof is shown as current/verified; released output falsely claims live current state; unauthorized evidence/OperationRun proof is accessible.
|
||||||
|
- **P1**: Critical behavior appears safe but lacks proof for current vs released, customer-safe boundary, OperationRun authorization, stale/failed/partial display, or evidence anchor scoping.
|
||||||
|
- **P2**: Non-critical productization debt where copy or lower-risk proof could be clearer without materially false claims.
|
||||||
|
- **P3**: Cleanup only, such as duplicate helper logic or minor non-customer-facing label polish.
|
||||||
|
|
||||||
|
## Candidate Gate Rules For Final Implementation Report
|
||||||
|
|
||||||
|
- **PASS**: no P0/P1 evidence/currentness proof gaps remain for high-risk customer/admin surfaces; customer-safe boundaries are proven; current vs released semantics are proven; targeted tests and browser proof pass; no new vocabulary was introduced.
|
||||||
|
- **PASS WITH CONDITIONS**: no P0 remains; remaining P1 items are explicitly deferred with safe justification; customer-facing claims are not overstated; critical current/released/customer-safe paths are proven.
|
||||||
|
- **FAIL**: any P0 remains; customer-safe output leaks internals; stale/failed/missing evidence is shown as current/verified; released output falsely claims live current state; unauthorized proof is accessible; browser proof cannot run for critical paths; fixes require unresolved product decisions beyond this spec.
|
||||||
|
|
||||||
|
## Follow-up Spec Candidates
|
||||||
|
|
||||||
|
- Spec 404 - Management Report PDF Staging Validation, only after Spec 403 passes or conditions are resolved.
|
||||||
|
- Governance artifact lifecycle/retention runtime.
|
||||||
|
- JSONB payload conversion and indexing for queryable evidence/report/audit payloads.
|
||||||
|
- Full browser/UX/runtime audit if later productization requires broad coverage.
|
||||||
|
- Provider readiness/onboarding productization if currentness closure exposes a provider setup friction gap.
|
||||||
|
|
||||||
|
## Assumptions
|
||||||
|
|
||||||
|
- The product remains pre-production, so false labels or fallback selectors can be cleanly corrected.
|
||||||
|
- Spec 402's `PASS` implementation report is accepted as clearing the authorization prerequisite for Spec 403.
|
||||||
|
- Existing evidence/currentness helpers from Specs 388 and 393 are repo truth until implementation inventory proves a gap.
|
||||||
|
- Browser support is available through the existing Pest browser setup; if not, the implementation report must record the exact blocker.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- None block implementation preparation.
|
||||||
|
- If implementation finds that an existing surface lacks a product decision for customer-safe proof or stale-provider evidence handling, classify it in the report rather than inventing behavior.
|
||||||
200
specs/403-evidence-anchor-currentness-runtime-closure/tasks.md
Normal file
200
specs/403-evidence-anchor-currentness-runtime-closure/tasks.md
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
# Tasks: Spec 403 - Evidence Anchor & Currentness Runtime Closure
|
||||||
|
|
||||||
|
**Input**: `specs/403-evidence-anchor-currentness-runtime-closure/spec.md`, `plan.md`, `checklists/requirements.md`, user-provided Spec 403 draft, Spec 400 context, Spec 402 implementation report, Product Surface Contract, and repo truth.
|
||||||
|
|
||||||
|
**Tests**: Required. This spec changes or verifies runtime evidence/currentness behavior and rendered product claims, so it must include focused Pest Unit/Feature/Filament tests plus focused browser proof for representative rendered paths.
|
||||||
|
|
||||||
|
**Completion note**: Tasks covering untouched downstream surfaces are closed by repo-truth inventory, the Evidence/Currentness Coverage Matrix, existing focused proof, and explicit P2 deferrals in `implementation-report.md`. Direct runtime edits were limited to Evidence Overview proof-state/currentness presentation, current-anchor missing/stale/empty-dimension guards, OperationRun default-link demotion, Customer Review Workspace canonical status and status-like decision-title presentation, canonical Evidence Inventory outcome mapping, and Evidence Snapshot artifact-truth classification for missing dimensions. Non-status action headings such as `Draft review exists` remain outside the canonical status-vocabulary claim.
|
||||||
|
|
||||||
|
## Test Governance Checklist
|
||||||
|
|
||||||
|
- [x] Lane assignment is named and is the narrowest sufficient proof for changed evidence/currentness behavior.
|
||||||
|
- [x] New or changed tests stay in focused Unit, Feature/Filament, and Browser families; heavy-governance additions are explicit if any.
|
||||||
|
- [x] Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default; any widening is isolated or documented.
|
||||||
|
- [x] Planned validation commands cover evidence/currentness closure without pulling unrelated full-suite cost.
|
||||||
|
- [x] The declared surface test profile or `standard-native-filament` relief is explicit.
|
||||||
|
- [x] Browser proof covers representative rendered evidence/currentness behavior and does not claim full browser audit.
|
||||||
|
- [x] Human Product Sanity and Product Surface implementation-report close-out are completed.
|
||||||
|
- [x] Any material budget, baseline, trend, or escalation note is recorded in the implementation report.
|
||||||
|
|
||||||
|
## Phase 1: Preparation And Dirty-State Baseline
|
||||||
|
|
||||||
|
**Purpose**: Establish safe starting conditions and read all governing context before runtime edits.
|
||||||
|
|
||||||
|
- [x] T001 Read `specs/403-evidence-anchor-currentness-runtime-closure/spec.md`, `plan.md`, `tasks.md`, and `checklists/requirements.md`.
|
||||||
|
- [x] T002 Record current branch, HEAD, dirty state, tracked changed files, untracked files, and `git diff --check` in `specs/403-evidence-anchor-currentness-runtime-closure/implementation-report.md`.
|
||||||
|
- [x] T003 Re-read `AGENTS.md`, `.specify/memory/constitution.md`, `.specify/README.md`, `docs/ai-coding-rules.md`, `docs/security-guidelines.md`, `docs/testing-guidelines.md`, `docs/architecture-guidelines.md`, `docs/filament-guidelines.md`, and `docs/product/standards/product-surface-contract.md`.
|
||||||
|
- [x] T004 Re-read `specs/388-resolution-proof-currentness-contract-v1/`, `specs/393-evidence-anchor-reconciliation-v1/`, `specs/400-product-contract-spec-completeness-audit/`, `specs/401-high-risk-admin-action-proof-pack/implementation-report.md`, and `specs/402-resource-policy-authorization-proof-matrix/implementation-report.md` as read-only context; preserve completed-spec history.
|
||||||
|
- [x] T005 Confirm Spec 402 has no unresolved P0/P1 authorization blocker before making Spec 403 runtime changes; record any residual authorization proof debt that affects evidence links.
|
||||||
|
- [x] T006 Confirm no new product vocabulary, routes, navigation, customer output category, report/PDF runtime, evidence provider, migration, package, env var, queue/scheduler/storage change, asset registration, or broad browser audit will be included.
|
||||||
|
|
||||||
|
## Phase 2: Repo Truth Inventory
|
||||||
|
|
||||||
|
**Purpose**: Build the matrix from current code and tests before fixing labels or helpers.
|
||||||
|
|
||||||
|
- [x] T007 Inventory evidence anchor/currentness helpers in `apps/platform/app/Services/Evidence/EvidenceAnchorResolver.php`, `EvidenceAnchorResult.php`, `EvidenceSnapshotResolver.php`, `EvidenceSnapshotService.php`, `apps/platform/app/Support/Evidence/EvidenceSnapshotStatus.php`, and `EvidenceCompletenessState.php`.
|
||||||
|
- [x] T008 Inventory Evidence Overview behavior in `apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php`, including current evidence link resolution, workspace-wide behavior, explicit environment filter behavior, empty states, and row URLs.
|
||||||
|
- [x] T009 Inventory Evidence Snapshot resource behavior in `apps/platform/app/Filament/Resources/EvidenceSnapshotResource.php` and nested pages, including stale/partial/failed/missing/expired display and authorization.
|
||||||
|
- [x] T010 Inventory Customer Review Workspace behavior in `apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php`, especially customer-safe evidence summaries, package/download state, internal proof suppression, and environment filters.
|
||||||
|
- [x] T011 Inventory Environment Review and Review Publication Resolution behavior in `apps/platform/app/Filament/Resources/EnvironmentReviewResource.php`, nested pages, and `resolve-review-publication.blade.php`, including Spec 388 proof-currentness fields.
|
||||||
|
- [x] T012 Inventory Review Pack and Stored Report behavior in `apps/platform/app/Filament/Resources/ReviewPackResource.php` and `StoredReportResource.php`, including generated/released evidence basis, report receipt/output links, and OperationRun proof links.
|
||||||
|
- [x] T013 Inventory OperationRun proof links and access checks in `OperationRunLinks`, `OperationRunPolicy`, Monitoring/Operations pages, and any proof links emitted by review/report/restore/baseline/finding surfaces.
|
||||||
|
- [x] T014 Inventory restore readiness/proof behavior in `apps/platform/app/Filament/Resources/RestoreRunResource.php`, restore presenters, and restore proof Blade views.
|
||||||
|
- [x] T015 Inventory baseline compare/evidence behavior in `BaselineCompareMatrix`, baseline resources, baseline evidence providers, and related tests.
|
||||||
|
- [x] T016 Inventory finding/governance evidence references in finding resources, finding exception evidence references, governance inbox/register surfaces, and related tests.
|
||||||
|
- [x] T017 Inventory existing tests under `apps/platform/tests/` for evidence overview/resource, Spec 388, Spec 393, customer review workspace, review packs, stored reports, OperationRun access, restore readiness, baseline evidence, finding evidence, and browser proof.
|
||||||
|
- [x] T018 Inventory repo-real provider freshness or permission-limited state contracts that already affect evidence quality/currentness, and record whether each contract is provider-owned diagnostic detail or platform-core evidence semantics.
|
||||||
|
|
||||||
|
## Phase 3: Evidence/Currentness Coverage Matrix
|
||||||
|
|
||||||
|
**Purpose**: Create the proof matrix before runtime fixes.
|
||||||
|
|
||||||
|
- [x] T019 Create `specs/403-evidence-anchor-currentness-runtime-closure/implementation-report.md` with sections A through M from `spec.md`.
|
||||||
|
- [x] T020 Add the Evidence/Currentness Coverage Matrix with columns: Surface, Evidence Source, Currentness Source, Released Snapshot Source, Customer-safe Boundary, Internal-only Data Risk, Workspace/Environment Scope, Authorization Mechanism, Test Proof, Browser Proof, Status, Risk, Follow-up.
|
||||||
|
- [x] T021 Classify Evidence Overview rows and links for current, stale, missing, failed, partial, expired, superseded, wrong-workspace, wrong-environment, unauthorized, and workspace-wide no-environment states.
|
||||||
|
- [x] T022 Classify Evidence Snapshot detail/list surfaces for stale, failed, partial, missing, expired, source/detail disclosure, and technical proof demotion.
|
||||||
|
- [x] T023 Classify Review Pack, Environment Review, Customer Review Workspace, and Stored Report surfaces for current runtime evidence versus released/generated evidence.
|
||||||
|
- [x] T024 Classify customer-safe boundaries for review workspace, review pack output, report output, and any customer-facing labels or downloads.
|
||||||
|
- [x] T025 Classify OperationRun proof links for authorization, workspace/environment scope, failed/running/cancelled/blocked/succeeded distinction, and customer-safe visibility.
|
||||||
|
- [x] T026 Classify findings/governance references for current, released/historical, missing, failed, blocked, or needs-attention evidence.
|
||||||
|
- [x] T027 Classify baseline compare and restore readiness/proof surfaces for stale/missing/failed/partial/expired currentness claims.
|
||||||
|
- [x] T028 Mark each matrix row as `PASS`, `PASS WITH EXCEPTION`, `MISSING PROOF`, `DEFECT FOUND`, `PRODUCT DECISION REQUIRED`, or `DEFERRED`, with P0/P1/P2/P3/None risk.
|
||||||
|
- [x] T029 Add matrix rows for any provider freshness or permission-limited evidence-currentness contracts discovered in T018, including source, customer-safe boundary, authorization mechanism, and test proof or deferral.
|
||||||
|
|
||||||
|
## Phase 4: Gap Classification
|
||||||
|
|
||||||
|
**Purpose**: Decide whether each matrix issue needs a test, a runtime fix, a product decision, or a deferral.
|
||||||
|
|
||||||
|
- [x] T030 Classify P0 defects where customer-safe output leaks internal proof, false currentness is shown, released output claims live/current state, or unauthorized evidence/OperationRun proof is accessible.
|
||||||
|
- [x] T031 Classify P1 missing proof where behavior may be safe but lacks direct tests for critical current/released/customer-safe/OperationRun/scoping paths.
|
||||||
|
- [x] T032 Classify P2/P3 productization or cleanup debt separately from safety blockers.
|
||||||
|
- [x] T033 Classify missing product decisions using the categories from the spec draft: blocks customer-output claim, blocks currentness claim, blocks internal proof claim, blocks review-pack/release claim, or can defer.
|
||||||
|
- [x] T034 Confirm no matrix gap is solved by inventing a new product vocabulary, new status family, new route, or new evidence taxonomy.
|
||||||
|
- [x] T035 Stop and update spec/plan before implementing if a fix requires new persistence, migrations, a broad proof framework, new evidence provider, report/PDF runtime, provider integration, or lifecycle semantics.
|
||||||
|
|
||||||
|
## Phase 5: Tests First - Current Evidence And Anchors
|
||||||
|
|
||||||
|
**Purpose**: Prove current evidence behavior before changing runtime code.
|
||||||
|
|
||||||
|
- [x] T036 Add or update focused tests for `EvidenceAnchorResolver` proving current evidence is selected only when active, complete, with usable captured dimensions, without missing/stale dimensions, non-expired, scoped to workspace/environment, and authorized.
|
||||||
|
- [x] T037 Add or update tests proving stale, expired, failed, partial, queued, generating, superseded, wrong-workspace, wrong-environment, and missing evidence cannot produce a current evidence link.
|
||||||
|
- [x] T038 Add or update Feature/Filament tests for `EvidenceOverview` proving workspace-wide views do not choose arbitrary current evidence and environment-filtered views link only authorized scoped current evidence.
|
||||||
|
- [x] T039 Add or update Evidence Snapshot resource tests proving stale/partial/failed/missing/expired evidence labels do not imply current/complete/verified proof.
|
||||||
|
- [x] T040 Add or update cross-workspace and cross-environment denial tests for evidence anchor direct URLs and proof links.
|
||||||
|
|
||||||
|
## Phase 6: Tests First - Released And Customer-Safe Proof
|
||||||
|
|
||||||
|
**Purpose**: Prove released/customer-safe behavior before runtime changes.
|
||||||
|
|
||||||
|
- [x] T041 Add or update tests proving released review-pack evidence stays bound to the released review/pack and does not query arbitrary latest current evidence.
|
||||||
|
- [x] T042 Add or update tests proving report receipts/output identify generated/released evidence and do not claim live/current runtime state unless an existing contract explicitly says so.
|
||||||
|
- [x] T043 Add or update Customer Review Workspace tests proving customer-safe output hides EvidenceSnapshot routes, evidence IDs, source keys, detectors, fingerprints, raw provider payloads, OperationRun URLs, internal reason families, and raw diagnostics by default.
|
||||||
|
- [x] T044 Add or update tests proving missing, failed, stale, expired, or partial released evidence is represented as `Not configured`, `Needs attention`, `Failed`, `Blocked`, or `Expired` rather than customer-safe ready.
|
||||||
|
- [x] T045 Add or update tests proving newer runtime evidence does not silently rewrite released review/report proof and does not invalidate released evidence without clear existing-contract labeling.
|
||||||
|
|
||||||
|
## Phase 7: Tests First - OperationRun, Restore, Baseline, Finding, And Report Proof
|
||||||
|
|
||||||
|
**Purpose**: Prove proof-link and downstream readiness claims are scoped and truthful.
|
||||||
|
|
||||||
|
- [x] T046 Add or update OperationRun proof tests proving failed, cancelled, blocked, running, stale, wrong-workspace, and wrong-environment runs cannot render as successful current proof.
|
||||||
|
- [x] T047 Add or update tests proving OperationRun proof links are demoted from the Evidence Overview default proof path and remain hidden or denied when `OperationRunPolicy` or environment entitlement does not allow access.
|
||||||
|
- [x] T048 Add or update restore readiness/proof tests proving stale/missing/failed/partial/expired preview/check/proof state is not presented as current executable readiness.
|
||||||
|
- [x] T049 Add or update baseline compare/evidence tests proving stale/missing/failed/partial baseline proof is not presented as current compare proof.
|
||||||
|
- [x] T050 Add or update finding/governance reference tests proving evidence references distinguish current, released/historical, missing, failed, blocked, or needs-attention proof where applicable.
|
||||||
|
- [x] T051 Add or update stored report/report output tests proving failed/missing/incomplete reports do not support customer-safe ready proof.
|
||||||
|
- [x] T052 Add or update tests proving provider freshness or permission-limited state affects evidence/currentness claims only where an existing repo contract connects that provider state to evidence quality, and is otherwise classified as product-decision or follow-up debt.
|
||||||
|
|
||||||
|
## Phase 8: Minimal Runtime Closure
|
||||||
|
|
||||||
|
**Purpose**: Fix only confirmed defects using existing architecture.
|
||||||
|
|
||||||
|
- [x] T053 Update existing evidence/currentness helpers or call sites only where tests prove a false, unsafe, or unscoped claim.
|
||||||
|
- [x] T054 Correct misleading labels that show stale, failed, partial, missing, expired, or released proof as current, complete, ready, verified, or live.
|
||||||
|
- [x] T055 Remove or replace arbitrary-latest evidence fallback selectors from product-facing current-evidence surfaces.
|
||||||
|
- [x] T056 Ensure customer-safe surfaces consume customer-safe summaries and never emit raw evidence/OperationRun technical links by default.
|
||||||
|
- [x] T057 Ensure released review/report surfaces use release-bound/generated evidence basis and label it separately from current runtime evidence.
|
||||||
|
- [x] T058 Ensure OperationRun proof is treated as execution/history proof, with default Evidence Overview links demoted and remaining technical OperationRun routes still using existing scoped URL helpers and policies.
|
||||||
|
- [x] T059 Ensure restore/baseline/finding/report proof labels consume existing readiness/evidence truth rather than inferring success from stale or partial data.
|
||||||
|
- [x] T060 Keep all Graph/provider calls out of render-time code paths.
|
||||||
|
- [x] T061 Do not add compatibility aliases, old labels, fallback readers, duplicate UI, or legacy fixtures that preserve wrong evidence/currentness behavior.
|
||||||
|
|
||||||
|
## Phase 9: Product Surface And Human Sanity
|
||||||
|
|
||||||
|
**Purpose**: Keep rendered behavior calm, customer-safe, and contract-compliant.
|
||||||
|
|
||||||
|
- [x] T062 Review and update `docs/ui-ux-enterprise-audit/route-inventory.md` and `docs/ui-ux-enterprise-audit/design-coverage-matrix.md` for touched existing surfaces if runtime UI files or reachable evidence/status semantics change; otherwise record that existing registry entries were reviewed and remain current.
|
||||||
|
- [x] T063 Confirm Product Surface Contract fields in `implementation-report.md`: no-legacy, UI impact, page archetype, surface budgets, Technical Annex demotion, canonical status vocabulary for proof/readiness and Evidence Inventory outcomes, Product Surface exceptions, visible complexity outcome, browser proof, Human Product Sanity, and UI coverage registry result.
|
||||||
|
- [x] T064 Confirm no Product Surface exception is required; if one is required, document page, violated rule/budget, reason, and follow-up.
|
||||||
|
- [x] T065 Run Human Product Sanity on touched customer-safe/readiness/evidence surfaces and record result.
|
||||||
|
- [x] T066 Confirm visible complexity is neutral or decreased; document any approved increase.
|
||||||
|
- [x] T067 Confirm no completed historical spec was rewritten, normalized, unchecked, or stripped of close-out/validation/browser history.
|
||||||
|
|
||||||
|
## Phase 10: Focused Browser Proof
|
||||||
|
|
||||||
|
**Purpose**: Verify representative rendered evidence/currentness behavior without claiming a full browser audit.
|
||||||
|
|
||||||
|
- [x] T068 Add or update focused browser smoke `apps/platform/tests/Browser/Spec403EvidenceCurrentnessRuntimeClosureSmokeTest.php` if browser support is available.
|
||||||
|
- [x] T069 Browser-proof admin Evidence Overview or Evidence Snapshot current/stale/missing/failed/partial behavior.
|
||||||
|
- [x] T070 Browser-proof Customer Review Workspace or review/report output customer-safe released proof behavior.
|
||||||
|
- [x] T071 Browser-proof released review/report evidence is not claimed as live current runtime state.
|
||||||
|
- [x] T072 Browser-proof stale/missing/failed evidence state path.
|
||||||
|
- [x] T073 Browser-proof unauthorized or cross-workspace/cross-environment evidence-anchor denial.
|
||||||
|
- [x] T074 Browser-proof OperationRun proof state and default-link demotion.
|
||||||
|
- [x] T075 Record route/surface, actor, workspace/environment, evidence state, expected result, observed result, console/runtime errors, and screenshot path if screenshots are captured.
|
||||||
|
- [x] T076 If browser tests are unavailable, record the exact blocker and do not claim browser proof.
|
||||||
|
|
||||||
|
## Phase 11: Implementation Report And Validation
|
||||||
|
|
||||||
|
**Purpose**: Close the proof loop with explicit result, residual severity, and next-step recommendation.
|
||||||
|
|
||||||
|
- [x] T077 Complete implementation report section A with Candidate Gate Result: PASS, PASS WITH CONDITIONS, or FAIL.
|
||||||
|
- [x] T078 Complete section B with included and explicitly not included scope.
|
||||||
|
- [x] T079 Complete section C with dirty state before/after, tracked files changed, and untracked files.
|
||||||
|
- [x] T080 Complete section D with the Evidence/Currentness Coverage Matrix.
|
||||||
|
- [x] T081 Complete section E with runtime changes made, why needed, and scope risk.
|
||||||
|
- [x] T082 Complete section F with tests added/updated, positive/negative classification, and result.
|
||||||
|
- [x] T083 Complete section G with focused browser proof or exact no-browser limitation.
|
||||||
|
- [x] T084 Complete section H with current vs released proof summary.
|
||||||
|
- [x] T085 Complete section I with customer-safe boundary proof summary.
|
||||||
|
- [x] T086 Complete section J with remaining findings by P0/P1/P2/P3.
|
||||||
|
- [x] T087 Complete section K with deferred items: management PDF staging validation, governance lifecycle/retention, JSONB migration, full browser audit, provider readiness productization, and other items.
|
||||||
|
- [x] T088 Complete the Filament v5 output contract close-out in `implementation-report.md`: Livewire v4 compliance, panel provider registration location, global-search posture for each touched resource, destructive/high-impact action confirmation and authorization posture, asset strategy, tests/browser result, and deployment impact.
|
||||||
|
- [x] T089 Complete section L with validation commands and exact results.
|
||||||
|
- [x] T090 Complete section M with recommended next action: Spec 404 only if Spec 403 passes or conditions are resolved.
|
||||||
|
- [x] T091 Run targeted Spec 403 tests and record result.
|
||||||
|
- [x] T092 Run targeted existing regressions for Evidence, Customer Review Workspace, Environment Review, Review Pack, Stored Report, OperationRun access, Restore, Baseline, and Finding surfaces changed by implementation.
|
||||||
|
- [x] T093 Run focused browser validation command if available and record result.
|
||||||
|
- [x] T094 Run formatter for changed PHP files and record result.
|
||||||
|
- [x] T095 Run `git diff --check` and record result.
|
||||||
|
- [x] T096 Verify changed reports, tests, logs, fixtures, screenshots, and implementation notes do not include secrets, tokens, raw credential payloads, or sensitive raw provider payloads.
|
||||||
|
- [x] T097 Run final dirty-state commands and confirm no unrelated dirty files were reset, deleted, or cleaned.
|
||||||
|
|
||||||
|
## Non-Goals Checklist
|
||||||
|
|
||||||
|
- [x] NT001 Do not add new product vocabulary, status family, evidence taxonomy, proof taxonomy, or currentness framework.
|
||||||
|
- [x] NT002 Do not add new admin, system, customer, navigation, report, PDF, evidence provider, restore, baseline, finding, or lifecycle surfaces.
|
||||||
|
- [x] NT003 Do not add migrations, JSON-to-JSONB changes, new persisted truth, packages, env vars, queues, scheduler changes, storage changes, or assets.
|
||||||
|
- [x] NT004 Do not perform broad service/model/Filament refactors.
|
||||||
|
- [x] NT005 Do not rewrite completed specs or remove historical close-out, validation, smoke, browser, or task history.
|
||||||
|
- [x] NT006 Do not claim full browser/UX/runtime audit completion.
|
||||||
|
- [x] NT007 Do not claim browser proof unless browser proof was actually run.
|
||||||
|
- [x] NT008 Do not proceed to Spec 404 recommendation if P0 remains or unresolved P1 evidence/currentness blockers are unsafe.
|
||||||
|
|
||||||
|
## Dependencies And Execution Order
|
||||||
|
|
||||||
|
- Phase 1 must complete before runtime edits.
|
||||||
|
- Phase 2 inventory must complete before Phase 3 matrix decisions.
|
||||||
|
- T018 must complete before T029 and before runtime fixes that rely on provider freshness or permission-limited evidence state.
|
||||||
|
- Phase 3 matrix must exist before Phase 4 gap classification.
|
||||||
|
- T029 must complete before provider-related P0/P1 gap classification is closed.
|
||||||
|
- Phase 4 must classify gaps before tests or runtime fixes.
|
||||||
|
- Phases 5-7 tests should precede Phase 8 fixes wherever feasible.
|
||||||
|
- T052 must precede any provider-freshness runtime correction.
|
||||||
|
- Phase 8 fixes must stay bounded to confirmed evidence/currentness gaps.
|
||||||
|
- T062 must complete before Product Surface close-out when runtime UI files or reachable evidence/status semantics change.
|
||||||
|
- Phase 10 browser proof follows focused hardening and tests.
|
||||||
|
- Phase 11 closes with report, validation, Filament v5 output contract close-out, dirty state, and next-step recommendation.
|
||||||
|
|
||||||
|
## Recommended Implementation Strategy
|
||||||
|
|
||||||
|
Treat implementation as a runtime truth-closure loop, not a framework pass. Build the matrix, add failing proof tests for confirmed P0/P1 risks, fix only the smallest currentness/evidence defects, and record exact proof. Preserve current repo helpers unless they demonstrably cannot express the required behavior.
|
||||||
Loading…
Reference in New Issue
Block a user