feat: implement pilot readiness remediation pack contract
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m13s
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m13s
This commit is contained in:
parent
3a0fc6c5c4
commit
84bb094e5e
@ -48,6 +48,7 @@ protected function getHeaderActions(): array
|
||||
return [
|
||||
Action::make('createWorkspace')
|
||||
->label('Create workspace')
|
||||
->visible(fn (): bool => $this->shouldShowCreateWorkspaceAction())
|
||||
->modalHeading('Create workspace')
|
||||
->form([
|
||||
TextInput::make('name')
|
||||
@ -64,6 +65,34 @@ protected function getHeaderActions(): array
|
||||
];
|
||||
}
|
||||
|
||||
public function accessHeading(): string
|
||||
{
|
||||
if ($this->isProviderConnectionNoAccess()) {
|
||||
return 'You do not have access to provider connections.';
|
||||
}
|
||||
|
||||
return 'You do not have access to a workspace yet.';
|
||||
}
|
||||
|
||||
public function accessBody(): string
|
||||
{
|
||||
if ($this->isProviderConnectionNoAccess()) {
|
||||
return 'You are signed in, but your current workspace or environment role does not include provider connection access. Ask an administrator to grant the required provider permission, or return to an area you can access.';
|
||||
}
|
||||
|
||||
return 'Ask an administrator to add you to a workspace, then sign in again.';
|
||||
}
|
||||
|
||||
public function shouldShowCreateWorkspaceAction(): bool
|
||||
{
|
||||
return ! $this->isProviderConnectionNoAccess();
|
||||
}
|
||||
|
||||
private function isProviderConnectionNoAccess(): bool
|
||||
{
|
||||
return request()->query('surface') === 'provider-connections';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{name: string, slug?: string|null} $data
|
||||
*/
|
||||
|
||||
@ -270,28 +270,6 @@ public static function infolist(Schema $schema): Schema
|
||||
->label('Source target')
|
||||
->state(fn (Finding $record): string => static::artifactDescriptorValue($record, 'source_target_kind'))
|
||||
->formatStateUsing(fn (string $state): string => Str::headline($state)),
|
||||
TextEntry::make('artifact_source_target_identifier')
|
||||
->label('Target identifier')
|
||||
->state(fn (Finding $record): ?string => static::artifactDescriptorNullableValue($record, 'source_target_identifier'))
|
||||
->copyable()
|
||||
->placeholder('—'),
|
||||
TextEntry::make('artifact_detector_key')
|
||||
->label('Detector')
|
||||
->state(fn (Finding $record): ?string => static::artifactDescriptorNullableValue($record, 'detector_key'))
|
||||
->placeholder('—'),
|
||||
TextEntry::make('artifact_control_key')
|
||||
->label('Control')
|
||||
->state(fn (Finding $record): ?string => static::artifactDescriptorNullableValue($record, 'control_key'))
|
||||
->formatStateUsing(fn (string $state): string => Str::headline($state))
|
||||
->placeholder('—'),
|
||||
TextEntry::make('artifact_provider_key')
|
||||
->label('Provider')
|
||||
->state(fn (Finding $record): string => static::artifactDescriptorValue($record, 'provider_key'))
|
||||
->formatStateUsing(fn (string $state): string => Str::headline($state)),
|
||||
TextEntry::make('artifact_provider_object_type')
|
||||
->label('Provider object type')
|
||||
->state(fn (Finding $record): ?string => static::artifactProviderDetailValue($record, 'provider_object_type'))
|
||||
->placeholder('—'),
|
||||
])
|
||||
->columns(2)
|
||||
->columnSpanFull(),
|
||||
@ -326,8 +304,6 @@ public static function infolist(Schema $schema): Schema
|
||||
->color(BadgeRenderer::color(BadgeDomain::FindingSeverity))
|
||||
->icon(BadgeRenderer::icon(BadgeDomain::FindingSeverity))
|
||||
->iconColor(BadgeRenderer::iconColor(BadgeDomain::FindingSeverity)),
|
||||
TextEntry::make('fingerprint')->label('Fingerprint')->copyable(),
|
||||
TextEntry::make('scope_key')->label('Scope')->copyable(),
|
||||
TextEntry::make('subject_display_name')
|
||||
->label('Subject')
|
||||
->placeholder('—')
|
||||
@ -335,21 +311,6 @@ public static function infolist(Schema $schema): Schema
|
||||
TextEntry::make('subject_type')
|
||||
->label('Subject type')
|
||||
->formatStateUsing(fn (mixed $state, Finding $record): string => static::subjectTypeLabel($record, $state)),
|
||||
TextEntry::make('subject_external_id')->label('External ID')->copyable(),
|
||||
TextEntry::make('baseline_operation_run_id')
|
||||
->label('Baseline run')
|
||||
->formatStateUsing(static fn (?int $state): string => $state ? '#'.$state : '—')
|
||||
->url(fn (Finding $record): ?string => $record->baseline_operation_run_id
|
||||
? OperationRunLinks::tenantlessView((int) $record->baseline_operation_run_id, static::findingRunNavigationContext($record))
|
||||
: null)
|
||||
->openUrlInNewTab(),
|
||||
TextEntry::make('current_operation_run_id')
|
||||
->label('Current run')
|
||||
->formatStateUsing(static fn (?int $state): string => $state ? '#'.$state : '—')
|
||||
->url(fn (Finding $record): ?string => $record->current_operation_run_id
|
||||
? OperationRunLinks::tenantlessView((int) $record->current_operation_run_id, static::findingRunNavigationContext($record))
|
||||
: null)
|
||||
->openUrlInNewTab(),
|
||||
TextEntry::make('first_seen_at')->label('First seen')->dateTime()->placeholder('—'),
|
||||
TextEntry::make('last_seen_at')->label('Last seen')->dateTime()->placeholder('—'),
|
||||
TextEntry::make('times_seen')->label('Times seen')->placeholder('—'),
|
||||
@ -497,6 +458,70 @@ public static function infolist(Schema $schema): Schema
|
||||
])
|
||||
->columnSpanFull(),
|
||||
|
||||
Section::make('Technical identifiers')
|
||||
->description('Support identifiers stay collapsed by default and are intended for authorized troubleshooting.')
|
||||
->schema([
|
||||
TextEntry::make('fingerprint')
|
||||
->label('Fingerprint')
|
||||
->copyable(),
|
||||
TextEntry::make('scope_key')
|
||||
->label('Scope key')
|
||||
->copyable(),
|
||||
TextEntry::make('source_fingerprint')
|
||||
->label('Source fingerprint')
|
||||
->state(fn (Finding $record): ?string => static::evidenceSourceFingerprint($record))
|
||||
->copyable()
|
||||
->placeholder('—')
|
||||
->visible(fn (Finding $record): bool => static::evidenceSourceFingerprint($record) !== null),
|
||||
TextEntry::make('subject_external_id')
|
||||
->label('Subject external ID')
|
||||
->copyable()
|
||||
->placeholder('—'),
|
||||
TextEntry::make('artifact_source_target_identifier')
|
||||
->label('Source target identifier')
|
||||
->state(fn (Finding $record): ?string => static::artifactDescriptorNullableValue($record, 'source_target_identifier'))
|
||||
->copyable()
|
||||
->placeholder('—'),
|
||||
TextEntry::make('artifact_detector_key')
|
||||
->label('Detector key')
|
||||
->state(fn (Finding $record): ?string => static::artifactDescriptorNullableValue($record, 'detector_key'))
|
||||
->copyable()
|
||||
->placeholder('—'),
|
||||
TextEntry::make('artifact_control_key')
|
||||
->label('Control key')
|
||||
->state(fn (Finding $record): ?string => static::artifactDescriptorNullableValue($record, 'control_key'))
|
||||
->copyable()
|
||||
->placeholder('—'),
|
||||
TextEntry::make('artifact_provider_key')
|
||||
->label('Provider key')
|
||||
->state(fn (Finding $record): ?string => static::artifactDescriptorNullableValue($record, 'provider_key'))
|
||||
->copyable()
|
||||
->placeholder('—'),
|
||||
TextEntry::make('artifact_provider_object_type')
|
||||
->label('Provider object type')
|
||||
->state(fn (Finding $record): ?string => static::artifactProviderDetailValue($record, 'provider_object_type'))
|
||||
->copyable()
|
||||
->placeholder('—'),
|
||||
TextEntry::make('baseline_operation_run_id')
|
||||
->label('Baseline run')
|
||||
->formatStateUsing(static fn (?int $state): string => $state ? '#'.$state : '—')
|
||||
->url(fn (Finding $record): ?string => $record->baseline_operation_run_id
|
||||
? OperationRunLinks::tenantlessView((int) $record->baseline_operation_run_id, static::findingRunNavigationContext($record))
|
||||
: null)
|
||||
->openUrlInNewTab(),
|
||||
TextEntry::make('current_operation_run_id')
|
||||
->label('Current run')
|
||||
->formatStateUsing(static fn (?int $state): string => $state ? '#'.$state : '—')
|
||||
->url(fn (Finding $record): ?string => $record->current_operation_run_id
|
||||
? OperationRunLinks::tenantlessView((int) $record->current_operation_run_id, static::findingRunNavigationContext($record))
|
||||
: null)
|
||||
->openUrlInNewTab(),
|
||||
])
|
||||
->columns(2)
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->columnSpanFull(),
|
||||
|
||||
Section::make('Diff')
|
||||
->visible(fn (Finding $record): bool => static::hasRenderableDiffSection($record))
|
||||
->schema([
|
||||
@ -584,6 +609,7 @@ public static function infolist(Schema $schema): Schema
|
||||
->columnSpanFull(),
|
||||
|
||||
Section::make('Evidence (Sanitized)')
|
||||
->description('Sanitized evidence JSON is collapsed so technical payload shape does not become default finding content.')
|
||||
->schema([
|
||||
ViewEntry::make('evidence_jsonb')
|
||||
->label('')
|
||||
@ -591,10 +617,31 @@ public static function infolist(Schema $schema): Schema
|
||||
->state(fn (Finding $record) => $record->evidence_jsonb ?? [])
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->collapsible()
|
||||
->collapsed()
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function evidenceSourceFingerprint(Finding $record): ?string
|
||||
{
|
||||
foreach ([
|
||||
'source_fingerprint',
|
||||
'summary.source_fingerprint',
|
||||
'baseline.source_fingerprint',
|
||||
'current.source_fingerprint',
|
||||
'artifact.source_fingerprint',
|
||||
] as $path) {
|
||||
$value = Arr::get($record->evidence_jsonb ?? [], $path);
|
||||
|
||||
if (is_string($value) && trim($value) !== '') {
|
||||
return trim($value);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function driftChangeType(Finding $record): string
|
||||
{
|
||||
$changeType = Arr::get($record->evidence_jsonb ?? [], 'change_type');
|
||||
|
||||
@ -58,6 +58,9 @@
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use UnitEnum;
|
||||
@ -126,6 +129,48 @@ public static function canCreate(): bool
|
||||
&& $resolver->can($user, $tenant, Capabilities::PROVIDER_MANAGE);
|
||||
}
|
||||
|
||||
public static function canViewAny(): bool
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
if (! $user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = Gate::forUser($user)->inspect('viewAny', ProviderConnection::class);
|
||||
|
||||
if ($response->allowed()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static::redirectProviderViewDenialWhenSafe($response->status());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function canView(Model $record): bool
|
||||
{
|
||||
if (! $record instanceof ProviderConnection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = auth()->user();
|
||||
|
||||
if (! $user instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = Gate::forUser($user)->inspect('view', $record);
|
||||
|
||||
if ($response->allowed()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static::redirectProviderViewDenialWhenSafe($response->status());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static function hasTenantCapability(string $capability): bool
|
||||
{
|
||||
$tenant = static::resolveScopedTenant();
|
||||
@ -142,6 +187,21 @@ protected static function hasTenantCapability(string $capability): bool
|
||||
&& $resolver->can($user, $tenant, $capability);
|
||||
}
|
||||
|
||||
private static function redirectProviderViewDenialWhenSafe(?int $status): void
|
||||
{
|
||||
if ($status === 404) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (preg_match('#^admin/provider-connections(?:/[0-9]+)?$#', request()->path()) !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new HttpResponseException(
|
||||
new RedirectResponse('/admin/no-access?surface=provider-connections&reason=permission')
|
||||
);
|
||||
}
|
||||
|
||||
protected static function resolveScopedTenant(): ?ManagedEnvironment
|
||||
{
|
||||
$tenantExternalId = static::resolveRequestedTenantExternalId();
|
||||
|
||||
@ -18,6 +18,8 @@ class ProviderConnectionPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
private const string VIEW_DENIED_MESSAGE = 'provider_connection_view_denied';
|
||||
|
||||
public function viewAny(User $user): Response|bool
|
||||
{
|
||||
$workspace = $this->currentWorkspace($user);
|
||||
@ -51,7 +53,7 @@ public function viewAny(User $user): Response|bool
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return Response::denyWithStatus(403, self::VIEW_DENIED_MESSAGE);
|
||||
}
|
||||
|
||||
public function view(User $user, ProviderConnection $connection): Response|bool
|
||||
@ -73,7 +75,7 @@ public function view(User $user, ProviderConnection $connection): Response|bool
|
||||
}
|
||||
|
||||
if (! Gate::forUser($user)->allows(Capabilities::PROVIDER_VIEW, $tenant)) {
|
||||
return false;
|
||||
return Response::denyWithStatus(403, self::VIEW_DENIED_MESSAGE);
|
||||
}
|
||||
|
||||
if ((int) $connection->managed_environment_id !== (int) $tenant->getKey()) {
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
use App\Support\ReviewPacks\ManagementReportPdfRuntimeGate;
|
||||
use App\Support\ReviewPacks\ReportProfileRegistry;
|
||||
use App\Support\ReviewPackStatus;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
@ -293,7 +294,9 @@ public function generateDownloadUrl(StoredReport $report, array $parameters = []
|
||||
|
||||
public function findReadyReport(ReviewPack $reviewPack): ?StoredReport
|
||||
{
|
||||
return StoredReport::query()
|
||||
$query = StoredReport::query()
|
||||
->where('workspace_id', (int) $reviewPack->workspace_id)
|
||||
->where('managed_environment_id', (int) $reviewPack->managed_environment_id)
|
||||
->where('source_review_pack_id', (int) $reviewPack->getKey())
|
||||
->where('report_type', StoredReport::REPORT_TYPE_MANAGEMENT_REPORT_PDF)
|
||||
->where('report_format', StoredReport::FORMAT_PDF)
|
||||
@ -302,7 +305,11 @@ public function findReadyReport(ReviewPack $reviewPack): ?StoredReport
|
||||
->where('file_disk', 'exports')
|
||||
->whereNotNull('file_path')
|
||||
->where('file_size', '>', 0)
|
||||
->whereNotNull('sha256')
|
||||
->whereNotNull('sha256');
|
||||
|
||||
$this->scopeToReviewPackSourceReview($query, $reviewPack);
|
||||
|
||||
return $query
|
||||
->latest('generated_at')
|
||||
->latest('id')
|
||||
->get()
|
||||
@ -311,8 +318,10 @@ public function findReadyReport(ReviewPack $reviewPack): ?StoredReport
|
||||
|
||||
public function findActiveReport(ReviewPack $reviewPack): ?StoredReport
|
||||
{
|
||||
return StoredReport::query()
|
||||
$query = StoredReport::query()
|
||||
->with('operationRun')
|
||||
->where('workspace_id', (int) $reviewPack->workspace_id)
|
||||
->where('managed_environment_id', (int) $reviewPack->managed_environment_id)
|
||||
->where('source_review_pack_id', (int) $reviewPack->getKey())
|
||||
->where('report_type', StoredReport::REPORT_TYPE_MANAGEMENT_REPORT_PDF)
|
||||
->where('report_format', StoredReport::FORMAT_PDF)
|
||||
@ -321,22 +330,35 @@ public function findActiveReport(ReviewPack $reviewPack): ?StoredReport
|
||||
->whereHas('operationRun', static function ($query): void {
|
||||
$query->whereIn('status', [OperationRunStatus::Queued->value, OperationRunStatus::Running->value]);
|
||||
})
|
||||
->latest('id')
|
||||
->latest('id');
|
||||
|
||||
$this->scopeToReviewPackSourceReview($query, $reviewPack);
|
||||
|
||||
return $query
|
||||
->first();
|
||||
}
|
||||
|
||||
public function findReportForRun(OperationRun $operationRun): ?StoredReport
|
||||
{
|
||||
return StoredReport::query()
|
||||
$query = StoredReport::query()
|
||||
->where('operation_run_id', (int) $operationRun->getKey())
|
||||
->where('workspace_id', (int) $operationRun->workspace_id)
|
||||
->where('managed_environment_id', (int) $operationRun->managed_environment_id)
|
||||
->where('report_type', StoredReport::REPORT_TYPE_MANAGEMENT_REPORT_PDF)
|
||||
->where('report_format', StoredReport::FORMAT_PDF)
|
||||
->where('profile', ReportProfileRegistry::CUSTOMER_EXECUTIVE);
|
||||
|
||||
$this->scopeToOperationRunSourceContext($query, $operationRun);
|
||||
|
||||
return $query
|
||||
->latest('id')
|
||||
->first();
|
||||
}
|
||||
|
||||
private function findRetryableReport(ReviewPack $reviewPack, string $fingerprint): ?StoredReport
|
||||
{
|
||||
return StoredReport::query()
|
||||
$query = StoredReport::query()
|
||||
->where('workspace_id', (int) $reviewPack->workspace_id)
|
||||
->where('managed_environment_id', (int) $reviewPack->managed_environment_id)
|
||||
->where('source_review_pack_id', (int) $reviewPack->getKey())
|
||||
->where('report_type', StoredReport::REPORT_TYPE_MANAGEMENT_REPORT_PDF)
|
||||
@ -344,10 +366,46 @@ private function findRetryableReport(ReviewPack $reviewPack, string $fingerprint
|
||||
->where('profile', ReportProfileRegistry::CUSTOMER_EXECUTIVE)
|
||||
->where('fingerprint', $fingerprint)
|
||||
->where('status', StoredReport::STATUS_FAILED)
|
||||
->latest('id')
|
||||
->latest('id');
|
||||
|
||||
$this->scopeToReviewPackSourceReview($query, $reviewPack);
|
||||
|
||||
return $query
|
||||
->first();
|
||||
}
|
||||
|
||||
private function scopeToReviewPackSourceReview(Builder $query, ReviewPack $reviewPack): void
|
||||
{
|
||||
if ($reviewPack->environment_review_id !== null) {
|
||||
$query->where('source_environment_review_id', (int) $reviewPack->environment_review_id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$query->whereNull('source_environment_review_id');
|
||||
}
|
||||
|
||||
private function scopeToOperationRunSourceContext(Builder $query, OperationRun $operationRun): void
|
||||
{
|
||||
$context = is_array($operationRun->context) ? $operationRun->context : [];
|
||||
|
||||
if (is_numeric($context['source_review_pack_id'] ?? null)) {
|
||||
$query->where('source_review_pack_id', (int) $context['source_review_pack_id']);
|
||||
}
|
||||
|
||||
if (! array_key_exists('source_environment_review_id', $context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_numeric($context['source_environment_review_id'])) {
|
||||
$query->where('source_environment_review_id', (int) $context['source_environment_review_id']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$query->whereNull('source_environment_review_id');
|
||||
}
|
||||
|
||||
private function hasReadablePdfArtifact(StoredReport $report): bool
|
||||
{
|
||||
if (! $report->isReadyManagementPdf()) {
|
||||
|
||||
@ -59,6 +59,7 @@ public function definitions(): array
|
||||
'tenant.evidence.snapshot.generate',
|
||||
'environment.review.compose',
|
||||
'environment.review_pack.generate',
|
||||
'report.management.generate',
|
||||
'backup_set.update',
|
||||
'backup.schedule.execute',
|
||||
'backup.schedule.retention',
|
||||
@ -68,7 +69,7 @@ public function definitions(): array
|
||||
policyIdentifier: 'artifact_or_later_success_v1',
|
||||
kind: 'artifact_or_later_success',
|
||||
supersededByCanonicalTypes: [$type],
|
||||
matchContextKeys: ['baseline_profile_id', 'backup_set_id', 'backup_schedule_id', 'selection_hash'],
|
||||
matchContextKeys: ['baseline_profile_id', 'backup_set_id', 'backup_schedule_id', 'source_review_pack_id', 'source_environment_review_id', 'selection_hash'],
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
use App\Models\EvidenceSnapshot;
|
||||
use App\Models\OperationRun;
|
||||
use App\Models\ReviewPack;
|
||||
use App\Services\ReviewPacks\ManagementReportPdfService;
|
||||
use App\Support\Baselines\BaselineSnapshotLifecycleState;
|
||||
use App\Support\Evidence\EvidenceCompletenessState;
|
||||
use App\Support\Evidence\EvidenceSnapshotStatus;
|
||||
@ -224,6 +225,7 @@ private function resolvingArtifact(OperationRun $run): ?array
|
||||
'tenant.evidence.snapshot.generate' => $this->evidenceSnapshotArtifact($run),
|
||||
'environment.review.compose' => $this->environmentReviewArtifact($run),
|
||||
'environment.review_pack.generate' => $this->reviewPackArtifact($run),
|
||||
'report.management.generate' => $this->managementReportArtifact($run),
|
||||
'backup_set.update', 'backup.schedule.execute', 'backup.schedule.retention' => $this->backupSetArtifact($run),
|
||||
default => null,
|
||||
};
|
||||
@ -328,6 +330,52 @@ private function reviewPackArtifact(OperationRun $run): ?array
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{type:string,id:int}|null
|
||||
*/
|
||||
private function managementReportArtifact(OperationRun $run): ?array
|
||||
{
|
||||
$reviewPackId = data_get($run->context, 'source_review_pack_id');
|
||||
$environmentReviewId = data_get($run->context, 'source_environment_review_id');
|
||||
|
||||
if (! is_numeric($reviewPackId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$reviewPack = ReviewPack::query()
|
||||
->whereKey((int) $reviewPackId)
|
||||
->where('workspace_id', (int) $run->workspace_id)
|
||||
->where('managed_environment_id', (int) $run->managed_environment_id)
|
||||
->first();
|
||||
|
||||
if (! $reviewPack instanceof ReviewPack) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$report = app(ManagementReportPdfService::class)->findReadyReport($reviewPack);
|
||||
|
||||
if ($report === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((int) $report->workspace_id !== (int) $run->workspace_id
|
||||
|| (int) $report->managed_environment_id !== (int) $run->managed_environment_id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_numeric($environmentReviewId) && (int) $report->source_environment_review_id !== (int) $environmentReviewId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$profile = data_get($run->context, 'profile');
|
||||
|
||||
if (is_string($profile) && trim($profile) !== '' && (string) $report->profile !== trim($profile)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ['type' => 'stored_report', 'id' => (int) $report->getKey()];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{type:string,id:int}|null
|
||||
*/
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
<x-filament::section>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="text-lg font-semibold text-gray-900 dark:text-gray-100">
|
||||
You don’t have access to any tenants yet.
|
||||
{{ $this->accessHeading() }}
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-600 dark:text-gray-300">
|
||||
Ask an administrator to add you to a tenant, then sign in again.
|
||||
{{ $this->accessBody() }}
|
||||
</div>
|
||||
</div>
|
||||
</x-filament::section>
|
||||
|
||||
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Filament\Resources\FindingResource;
|
||||
use App\Models\Finding;
|
||||
use App\Models\ManagedEnvironment;
|
||||
use App\Models\ProviderConnection;
|
||||
use App\Models\User;
|
||||
use App\Support\Workspaces\WorkspaceContext;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
pest()->browser()->timeout(60_000);
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('Spec412 smokes finding detail hash demotion and provider no-access clarity', function (): void {
|
||||
[$operator, $environment] = createUserWithTenant(
|
||||
role: 'owner',
|
||||
workspaceRole: 'owner',
|
||||
clearCapabilityCaches: true,
|
||||
);
|
||||
|
||||
$finding = Finding::factory()->for($environment)->create([
|
||||
'workspace_id' => (int) $environment->workspace_id,
|
||||
'fingerprint' => 'spec412-browser-fingerprint-hidden',
|
||||
'scope_key' => 'spec412-browser-scope-hidden',
|
||||
'subject_external_id' => 'spec412-browser-subject-hidden',
|
||||
'evidence_jsonb' => [
|
||||
'display_name' => 'Spec412 Browser Human Finding',
|
||||
'summary' => [
|
||||
'source_fingerprint' => 'spec412-browser-source-hidden',
|
||||
'affected_scope' => 'Human-readable pilot scope',
|
||||
],
|
||||
'artifact' => [
|
||||
'detector_key' => 'spec412-browser-detector-hidden',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
spec412AuthenticateBrowser($this, $operator, $environment);
|
||||
|
||||
visit(FindingResource::getUrl('view', ['record' => $finding], tenant: $environment, panel: 'admin'))
|
||||
->resize(1440, 1100)
|
||||
->waitForText('Spec412 Browser Human Finding')
|
||||
->assertSee('Technical identifiers')
|
||||
->assertSee('Support identifiers stay collapsed by default and are intended for authorized troubleshooting.')
|
||||
->assertSee('Evidence (Sanitized)')
|
||||
->assertSee('Sanitized evidence JSON is collapsed so technical payload shape does not become default finding content.')
|
||||
->assertDontSee('spec412-browser-fingerprint-hidden')
|
||||
->assertDontSee('spec412-browser-scope-hidden')
|
||||
->assertDontSee('spec412-browser-source-hidden')
|
||||
->assertDontSee('spec412-browser-subject-hidden')
|
||||
->assertNoJavaScriptErrors()
|
||||
->assertNoConsoleLogs();
|
||||
|
||||
$connection = ProviderConnection::factory()
|
||||
->platform()
|
||||
->verifiedHealthy()
|
||||
->create([
|
||||
'workspace_id' => (int) $environment->workspace_id,
|
||||
'managed_environment_id' => (int) $environment->getKey(),
|
||||
'display_name' => 'Spec412 Browser Provider',
|
||||
]);
|
||||
|
||||
[$readonly] = createUserWithTenant(
|
||||
tenant: $environment,
|
||||
role: 'readonly',
|
||||
workspaceRole: 'readonly',
|
||||
clearCapabilityCaches: true,
|
||||
);
|
||||
|
||||
visit(spec412BrowserLoginUrl($readonly, $environment, '/admin/no-access?surface=provider-connections&reason=permission'))
|
||||
->resize(1440, 1000)
|
||||
->waitForText('You do not have access to provider connections.')
|
||||
->assertSee('You are signed in, but your current workspace or environment role does not include provider connection access.')
|
||||
->assertDontSee('You do not have access to a workspace yet.')
|
||||
->assertDontSee('Ask an administrator to add you to a workspace, then sign in again.')
|
||||
->assertDontSee('Spec412 Browser Provider')
|
||||
->assertNoJavaScriptErrors()
|
||||
->assertNoConsoleLogs();
|
||||
});
|
||||
|
||||
function spec412AuthenticateBrowser(
|
||||
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 spec412BrowserLoginUrl(User $user, ManagedEnvironment $environment, string $redirect): string
|
||||
{
|
||||
return route('admin.local.smoke-login', [
|
||||
'email' => $user->email,
|
||||
'tenant' => $environment->external_id,
|
||||
'workspace' => $environment->workspace->slug,
|
||||
'redirect' => spec412RelativeBrowserPath($redirect),
|
||||
]);
|
||||
}
|
||||
|
||||
function spec412RelativeBrowserPath(string $url): string
|
||||
{
|
||||
$parts = parse_url($url);
|
||||
|
||||
if ($parts === false) {
|
||||
return '/admin';
|
||||
}
|
||||
|
||||
return ($parts['path'] ?? '/admin').(isset($parts['query']) ? '?'.$parts['query'] : '');
|
||||
}
|
||||
@ -6,6 +6,19 @@
|
||||
use App\Models\Policy;
|
||||
use App\Models\PolicyVersion;
|
||||
|
||||
if (! function_exists('spec412FindingResourceSectionBlock')) {
|
||||
function spec412FindingResourceSectionBlock(string $source, string $heading): string
|
||||
{
|
||||
$start = strpos($source, "Section::make('{$heading}')");
|
||||
|
||||
expect($start)->not->toBeFalse();
|
||||
|
||||
$next = strpos($source, "\n\n Section::make(", $start + strlen($heading));
|
||||
|
||||
return substr($source, $start, $next === false ? null : $next - $start);
|
||||
}
|
||||
}
|
||||
|
||||
test('finding detail renders without Graph calls', function () {
|
||||
bindFailHardGraphClient();
|
||||
|
||||
@ -44,10 +57,41 @@
|
||||
$this->actingAs($user)
|
||||
->get(FindingResource::getUrl('view', ['record' => $finding], tenant: $tenant))
|
||||
->assertOk()
|
||||
->assertSee('Technical identifiers')
|
||||
->assertSee($finding->fingerprint)
|
||||
->assertSee($inventoryItem->display_name);
|
||||
});
|
||||
|
||||
test('finding detail demotes raw technical identifiers from default sections', function (): void {
|
||||
$source = (string) file_get_contents(repo_path('apps/platform/app/Filament/Resources/FindingResource.php'));
|
||||
|
||||
$artifactSourceSection = spec412FindingResourceSectionBlock($source, 'Artifact source');
|
||||
$findingSection = spec412FindingResourceSectionBlock($source, 'Finding');
|
||||
$technicalSection = spec412FindingResourceSectionBlock($source, 'Technical identifiers');
|
||||
$evidenceSection = spec412FindingResourceSectionBlock($source, 'Evidence (Sanitized)');
|
||||
|
||||
expect($artifactSourceSection)
|
||||
->not->toContain("TextEntry::make('artifact_detector_key')")
|
||||
->not->toContain("TextEntry::make('artifact_source_target_identifier')")
|
||||
->not->toContain("TextEntry::make('artifact_provider_key')")
|
||||
->and($findingSection)
|
||||
->not->toContain("TextEntry::make('fingerprint')")
|
||||
->not->toContain("TextEntry::make('scope_key')")
|
||||
->not->toContain("TextEntry::make('subject_external_id')")
|
||||
->not->toContain("TextEntry::make('baseline_operation_run_id')")
|
||||
->not->toContain("TextEntry::make('current_operation_run_id')")
|
||||
->and($technicalSection)
|
||||
->toContain("TextEntry::make('fingerprint')")
|
||||
->toContain("TextEntry::make('scope_key')")
|
||||
->toContain("TextEntry::make('source_fingerprint')")
|
||||
->toContain("TextEntry::make('artifact_detector_key')")
|
||||
->toContain("TextEntry::make('baseline_operation_run_id')")
|
||||
->toContain('->collapsed()')
|
||||
->and($evidenceSection)
|
||||
->toContain("Section::make('Evidence (Sanitized)')")
|
||||
->toContain('->collapsed()');
|
||||
});
|
||||
|
||||
test('finding detail explains protected changes without exposing hidden values', function () {
|
||||
bindFailHardGraphClient();
|
||||
|
||||
|
||||
@ -6,10 +6,14 @@
|
||||
use App\Models\ManagedEnvironment;
|
||||
use App\Models\OperationRun;
|
||||
use App\Models\ProviderConnection;
|
||||
use App\Models\ReviewPack;
|
||||
use App\Models\StoredReport;
|
||||
use App\Support\OperationRunOutcome;
|
||||
use App\Support\OperationRunStatus;
|
||||
use App\Support\Operations\Actionability\OperationRunActionabilityResolver;
|
||||
use App\Support\Operations\Actionability\OperationRunActionabilityStatus;
|
||||
use App\Support\ReviewPacks\ReportProfileRegistry;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
it('resolves old provider connection blockers when the same connection is currently healthy in Spec367', function (): void {
|
||||
$tenant = ManagedEnvironment::factory()->create();
|
||||
@ -158,6 +162,127 @@
|
||||
->and($result->requiresCurrentFollowUp())->toBeFalse();
|
||||
});
|
||||
|
||||
it('uses ready management PDF artifact proof to resolve terminal management report generation history in Spec412', function (): void {
|
||||
Storage::fake('exports');
|
||||
|
||||
$tenant = ManagedEnvironment::factory()->create();
|
||||
$pack = ReviewPack::factory()->ready()->create([
|
||||
'workspace_id' => (int) $tenant->workspace_id,
|
||||
'managed_environment_id' => (int) $tenant->getKey(),
|
||||
]);
|
||||
$pdfBytes = spec367ManagementReportPdfBytes('Spec367 ready management report');
|
||||
$filePath = sprintf('management-reports/spec367/%d-ready.pdf', (int) $pack->getKey());
|
||||
Storage::disk('exports')->put($filePath, $pdfBytes);
|
||||
|
||||
$run = spec367TerminalRun($tenant, [
|
||||
'type' => 'report.management.generate',
|
||||
'outcome' => OperationRunOutcome::Failed->value,
|
||||
'context' => [
|
||||
'source_review_pack_id' => (int) $pack->getKey(),
|
||||
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
||||
'report_type' => StoredReport::REPORT_TYPE_MANAGEMENT_REPORT_PDF,
|
||||
],
|
||||
]);
|
||||
|
||||
$report = StoredReport::factory()->managementReportPdf()->create([
|
||||
'workspace_id' => (int) $tenant->workspace_id,
|
||||
'managed_environment_id' => (int) $tenant->getKey(),
|
||||
'source_review_pack_id' => (int) $pack->getKey(),
|
||||
'operation_run_id' => null,
|
||||
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
||||
'file_path' => $filePath,
|
||||
'file_size' => strlen($pdfBytes),
|
||||
'sha256' => hash('sha256', $pdfBytes),
|
||||
]);
|
||||
|
||||
$result = app(OperationRunActionabilityResolver::class)->evaluate($run->fresh());
|
||||
|
||||
expect($result->status)->toBe(OperationRunActionabilityStatus::ResolvedByCurrentState)
|
||||
->and($result->resolvingModelType)->toBe('stored_report')
|
||||
->and($result->resolvingModelId)->toBe((int) $report->getKey())
|
||||
->and($result->requiresCurrentFollowUp())->toBeFalse()
|
||||
->and(OperationRun::query()->whereKey($run->getKey())->currentTerminalFollowUp()->exists())->toBeFalse();
|
||||
});
|
||||
|
||||
it('keeps terminal management report generation actionable when ready metadata points to a missing PDF in Spec412', function (): void {
|
||||
Storage::fake('exports');
|
||||
|
||||
$tenant = ManagedEnvironment::factory()->create();
|
||||
$pack = ReviewPack::factory()->ready()->create([
|
||||
'workspace_id' => (int) $tenant->workspace_id,
|
||||
'managed_environment_id' => (int) $tenant->getKey(),
|
||||
]);
|
||||
$pdfBytes = spec367ManagementReportPdfBytes('Spec367 missing management report');
|
||||
|
||||
$run = spec367TerminalRun($tenant, [
|
||||
'type' => 'report.management.generate',
|
||||
'outcome' => OperationRunOutcome::Failed->value,
|
||||
'context' => [
|
||||
'source_review_pack_id' => (int) $pack->getKey(),
|
||||
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
||||
'report_type' => StoredReport::REPORT_TYPE_MANAGEMENT_REPORT_PDF,
|
||||
],
|
||||
]);
|
||||
|
||||
StoredReport::factory()->managementReportPdf()->create([
|
||||
'workspace_id' => (int) $tenant->workspace_id,
|
||||
'managed_environment_id' => (int) $tenant->getKey(),
|
||||
'source_review_pack_id' => (int) $pack->getKey(),
|
||||
'operation_run_id' => null,
|
||||
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
||||
'file_path' => sprintf('management-reports/spec367/%d-missing.pdf', (int) $pack->getKey()),
|
||||
'file_size' => strlen($pdfBytes),
|
||||
'sha256' => hash('sha256', $pdfBytes),
|
||||
]);
|
||||
|
||||
$result = app(OperationRunActionabilityResolver::class)->evaluate($run->fresh());
|
||||
|
||||
expect($result->status)->toBe(OperationRunActionabilityStatus::Actionable)
|
||||
->and($result->requiresCurrentFollowUp())->toBeTrue()
|
||||
->and(OperationRun::query()->whereKey($run->getKey())->currentTerminalFollowUp()->exists())->toBeTrue();
|
||||
});
|
||||
|
||||
it('keeps terminal management report generation actionable when the ready PDF belongs to another tenant scope in Spec412', function (): void {
|
||||
Storage::fake('exports');
|
||||
|
||||
$tenant = ManagedEnvironment::factory()->create();
|
||||
$otherTenant = ManagedEnvironment::factory()->create();
|
||||
$pack = ReviewPack::factory()->ready()->create([
|
||||
'workspace_id' => (int) $tenant->workspace_id,
|
||||
'managed_environment_id' => (int) $tenant->getKey(),
|
||||
]);
|
||||
$pdfBytes = spec367ManagementReportPdfBytes('Spec367 cross tenant management report');
|
||||
$filePath = sprintf('management-reports/spec367/%d-cross-tenant.pdf', (int) $pack->getKey());
|
||||
Storage::disk('exports')->put($filePath, $pdfBytes);
|
||||
|
||||
$run = spec367TerminalRun($tenant, [
|
||||
'type' => 'report.management.generate',
|
||||
'outcome' => OperationRunOutcome::Failed->value,
|
||||
'context' => [
|
||||
'source_review_pack_id' => (int) $pack->getKey(),
|
||||
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
||||
'report_type' => StoredReport::REPORT_TYPE_MANAGEMENT_REPORT_PDF,
|
||||
],
|
||||
]);
|
||||
|
||||
StoredReport::factory()->managementReportPdf()->create([
|
||||
'workspace_id' => (int) $otherTenant->workspace_id,
|
||||
'managed_environment_id' => (int) $otherTenant->getKey(),
|
||||
'source_review_pack_id' => (int) $pack->getKey(),
|
||||
'operation_run_id' => null,
|
||||
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
||||
'file_path' => $filePath,
|
||||
'file_size' => strlen($pdfBytes),
|
||||
'sha256' => hash('sha256', $pdfBytes),
|
||||
]);
|
||||
|
||||
$result = app(OperationRunActionabilityResolver::class)->evaluate($run->fresh());
|
||||
|
||||
expect($result->status)->toBe(OperationRunActionabilityStatus::Actionable)
|
||||
->and($result->requiresCurrentFollowUp())->toBeTrue()
|
||||
->and(OperationRun::query()->whereKey($run->getKey())->currentTerminalFollowUp()->exists())->toBeTrue();
|
||||
});
|
||||
|
||||
it('keeps high-risk terminal operations in manual review even when later runs succeed in Spec367', function (): void {
|
||||
$tenant = ManagedEnvironment::factory()->create();
|
||||
|
||||
@ -236,3 +361,8 @@ function spec367TerminalRun(ManagedEnvironment $tenant, array $attributes = []):
|
||||
'context' => [],
|
||||
], $attributes));
|
||||
}
|
||||
|
||||
function spec367ManagementReportPdfBytes(string $label): string
|
||||
{
|
||||
return "%PDF-1.7\n1 0 obj\n<< /Type /Catalog /Title ({$label}) >>\nendobj\nstartxref\n0\n%%EOF";
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
use App\Support\Auth\Capabilities;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
it('returns 403 for tenant members without PROVIDER_VIEW capability on list and detail', function (): void {
|
||||
it('routes tenant members without PROVIDER_VIEW capability to clear no-access copy on list and detail', function (): void {
|
||||
[$user, $tenant] = createUserWithTenant(role: 'readonly');
|
||||
|
||||
Gate::define(Capabilities::PROVIDER_VIEW, fn () => false);
|
||||
@ -19,9 +19,17 @@
|
||||
|
||||
$this->actingAs($user)
|
||||
->get('/admin/provider-connections')
|
||||
->assertForbidden();
|
||||
->assertRedirect('/admin/no-access?surface=provider-connections&reason=permission');
|
||||
|
||||
$this->actingAs($user)
|
||||
->get('/admin/provider-connections/'.$connection->getKey())
|
||||
->assertForbidden();
|
||||
->assertRedirect('/admin/no-access?surface=provider-connections&reason=permission');
|
||||
|
||||
$this->actingAs($user)
|
||||
->get('/admin/no-access?surface=provider-connections&reason=permission')
|
||||
->assertOk()
|
||||
->assertSee('You do not have access to provider connections.')
|
||||
->assertSee('You are signed in, but your current workspace or environment role does not include provider connection access.')
|
||||
->assertDontSee('You do not have access to a workspace yet.')
|
||||
->assertDontSee('then sign in again');
|
||||
});
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
use App\Jobs\GenerateManagementReportPdfJob;
|
||||
use App\Jobs\GenerateReviewPackJob;
|
||||
use App\Models\AuditLog;
|
||||
use App\Models\EnvironmentReview;
|
||||
use App\Models\ManagedEnvironment;
|
||||
use App\Models\OperationRun;
|
||||
use App\Models\ReviewPack;
|
||||
@ -207,6 +208,34 @@ function spec379ReadyManagementPdf(ReviewPack $pack): StoredReport
|
||||
]);
|
||||
}
|
||||
|
||||
function spec379ReadableManagementPdfRecord(ReviewPack $pack, string $label, array $overrides = []): StoredReport
|
||||
{
|
||||
$pdfBytes = spec379PdfBytes($label);
|
||||
$filePath = sprintf(
|
||||
'management-reports/%s/%s-%d.pdf',
|
||||
$pack->tenant->external_id,
|
||||
str($label)->slug()->toString(),
|
||||
(int) $pack->getKey(),
|
||||
);
|
||||
|
||||
Storage::disk('exports')->put($filePath, $pdfBytes);
|
||||
|
||||
return StoredReport::factory()->managementReportPdf([
|
||||
'title' => $label,
|
||||
])->create(array_replace([
|
||||
'workspace_id' => (int) $pack->workspace_id,
|
||||
'managed_environment_id' => (int) $pack->managed_environment_id,
|
||||
'source_environment_review_id' => $pack->environment_review_id !== null ? (int) $pack->environment_review_id : null,
|
||||
'source_review_pack_id' => (int) $pack->getKey(),
|
||||
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
||||
'file_disk' => 'exports',
|
||||
'file_path' => $filePath,
|
||||
'file_size' => strlen($pdfBytes),
|
||||
'sha256' => hash('sha256', $pdfBytes),
|
||||
'generated_at' => now(),
|
||||
], $overrides));
|
||||
}
|
||||
|
||||
it('Spec379 blocks generation until the PDF runtime is validated', function (): void {
|
||||
spec379ConfigurePdfRenderer(runtimeValidated: false);
|
||||
[$user, $tenant, , $pack] = spec379CurrentReadyPack();
|
||||
@ -699,3 +728,159 @@ function spec379ReadyManagementPdf(ReviewPack $pack): StoredReport
|
||||
->assertActionVisible('download_management_report_pdf')
|
||||
->assertActionDoesNotExist('generate_management_report_pdf');
|
||||
});
|
||||
|
||||
it('Spec412 ignores readable management PDF records that do not match the review pack scope', function (): void {
|
||||
spec379ConfigurePdfRenderer();
|
||||
[$user, $tenant, $review, $pack] = spec379CurrentReadyPack();
|
||||
$otherTenant = ManagedEnvironment::factory()->create();
|
||||
$otherReview = EnvironmentReview::factory()->ready()->create([
|
||||
'workspace_id' => (int) $tenant->workspace_id,
|
||||
'managed_environment_id' => (int) $tenant->getKey(),
|
||||
'evidence_snapshot_id' => (int) $review->evidence_snapshot_id,
|
||||
'initiated_by_user_id' => (int) $user->getKey(),
|
||||
]);
|
||||
|
||||
spec379ReadableManagementPdfRecord($pack, 'Spec412 cross tenant report', [
|
||||
'workspace_id' => (int) $otherTenant->workspace_id,
|
||||
'managed_environment_id' => (int) $otherTenant->getKey(),
|
||||
'generated_at' => now()->subMinutes(3),
|
||||
]);
|
||||
|
||||
spec379ReadableManagementPdfRecord($pack, 'Spec412 wrong source review report', [
|
||||
'source_environment_review_id' => (int) $otherReview->getKey(),
|
||||
'generated_at' => now()->subMinutes(2),
|
||||
]);
|
||||
|
||||
spec379ReadableManagementPdfRecord($pack, 'Spec412 missing source review report', [
|
||||
'source_environment_review_id' => null,
|
||||
'generated_at' => now()->subMinute(),
|
||||
]);
|
||||
|
||||
$service = app(ManagementReportPdfService::class);
|
||||
|
||||
expect($service->findReadyReport($pack))->toBeNull()
|
||||
->and($service->generationDecision($pack)['state'])->toBe('available');
|
||||
|
||||
setAdminEnvironmentContext($tenant);
|
||||
|
||||
Livewire::actingAs($user)
|
||||
->test(ViewReviewPack::class, ['record' => $pack->getKey()])
|
||||
->assertActionDoesNotExist('download_management_report_pdf')
|
||||
->assertActionVisible('generate_management_report_pdf');
|
||||
});
|
||||
|
||||
it('Spec412 ignores active management PDF records that do not match the review pack scope', function (): void {
|
||||
spec379ConfigurePdfRenderer();
|
||||
[$user, $tenant, , $pack] = spec379CurrentReadyPack();
|
||||
$otherTenant = ManagedEnvironment::factory()->create();
|
||||
$foreignRun = OperationRun::factory()->forTenant($otherTenant)->create([
|
||||
'type' => OperationRunType::ManagementReportGenerate->value,
|
||||
'status' => OperationRunStatus::Queued->value,
|
||||
'outcome' => OperationRunOutcome::Pending->value,
|
||||
]);
|
||||
|
||||
StoredReport::factory()->managementReportPdf()->create([
|
||||
'workspace_id' => (int) $otherTenant->workspace_id,
|
||||
'managed_environment_id' => (int) $otherTenant->getKey(),
|
||||
'source_environment_review_id' => (int) $pack->environment_review_id,
|
||||
'source_review_pack_id' => (int) $pack->getKey(),
|
||||
'operation_run_id' => (int) $foreignRun->getKey(),
|
||||
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
||||
'status' => StoredReport::STATUS_GENERATING,
|
||||
]);
|
||||
|
||||
expect(app(ManagementReportPdfService::class)->findActiveReport($pack))->toBeNull();
|
||||
|
||||
setAdminEnvironmentContext($tenant);
|
||||
|
||||
Livewire::actingAs($user)
|
||||
->test(ViewReviewPack::class, ['record' => $pack->getKey()])
|
||||
->assertActionDoesNotExist('open_management_report_pdf_operation')
|
||||
->assertActionVisible('generate_management_report_pdf');
|
||||
});
|
||||
|
||||
it('Spec412 does not reuse failed management PDF records outside the review pack scope', function (): void {
|
||||
spec379ConfigurePdfRenderer();
|
||||
Queue::fake();
|
||||
[$user, $tenant, , $pack] = spec379CurrentReadyPack();
|
||||
$otherTenant = ManagedEnvironment::factory()->create();
|
||||
$service = app(ManagementReportPdfService::class);
|
||||
$fingerprint = $service->computeFingerprint($pack);
|
||||
$foreignFailedReport = StoredReport::factory()->managementReportPdf()->create([
|
||||
'workspace_id' => (int) $otherTenant->workspace_id,
|
||||
'managed_environment_id' => (int) $otherTenant->getKey(),
|
||||
'source_environment_review_id' => (int) $pack->environment_review_id,
|
||||
'source_review_pack_id' => (int) $pack->getKey(),
|
||||
'operation_run_id' => null,
|
||||
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
||||
'status' => StoredReport::STATUS_FAILED,
|
||||
'fingerprint' => $fingerprint,
|
||||
'file_disk' => null,
|
||||
'file_path' => null,
|
||||
'file_size' => null,
|
||||
'sha256' => null,
|
||||
'generated_at' => null,
|
||||
]);
|
||||
|
||||
$result = $service->startGeneration($pack, $user);
|
||||
$report = $result['report'] ?? null;
|
||||
|
||||
expect($report)->toBeInstanceOf(StoredReport::class)
|
||||
->and((int) $report->getKey())->not->toBe((int) $foreignFailedReport->getKey())
|
||||
->and((int) $report->workspace_id)->toBe((int) $tenant->workspace_id)
|
||||
->and((int) $report->managed_environment_id)->toBe((int) $tenant->getKey())
|
||||
->and((int) $report->source_review_pack_id)->toBe((int) $pack->getKey())
|
||||
->and((int) $report->source_environment_review_id)->toBe((int) $pack->environment_review_id)
|
||||
->and($foreignFailedReport->fresh()->status)->toBe(StoredReport::STATUS_FAILED);
|
||||
});
|
||||
|
||||
it('Spec412 ignores operation-run-bound management PDF records outside the run scope', function (): void {
|
||||
[$user, $tenant, $review, $pack] = spec379CurrentReadyPack();
|
||||
$otherTenant = ManagedEnvironment::factory()->create();
|
||||
$otherReview = EnvironmentReview::factory()->ready()->create([
|
||||
'workspace_id' => (int) $tenant->workspace_id,
|
||||
'managed_environment_id' => (int) $tenant->getKey(),
|
||||
'evidence_snapshot_id' => (int) $review->evidence_snapshot_id,
|
||||
'initiated_by_user_id' => (int) $user->getKey(),
|
||||
]);
|
||||
$otherPack = ReviewPack::factory()->ready()->create([
|
||||
'workspace_id' => (int) $tenant->workspace_id,
|
||||
'managed_environment_id' => (int) $tenant->getKey(),
|
||||
'environment_review_id' => (int) $otherReview->getKey(),
|
||||
'evidence_snapshot_id' => (int) $review->evidence_snapshot_id,
|
||||
'initiated_by_user_id' => (int) $user->getKey(),
|
||||
]);
|
||||
$run = OperationRun::factory()->forTenant($tenant)->create([
|
||||
'type' => OperationRunType::ManagementReportGenerate->value,
|
||||
'status' => OperationRunStatus::Queued->value,
|
||||
'outcome' => OperationRunOutcome::Pending->value,
|
||||
'context' => [
|
||||
'source_review_pack_id' => (int) $pack->getKey(),
|
||||
'source_environment_review_id' => (int) $pack->environment_review_id,
|
||||
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
||||
'report_type' => StoredReport::REPORT_TYPE_MANAGEMENT_REPORT_PDF,
|
||||
],
|
||||
]);
|
||||
|
||||
StoredReport::factory()->managementReportPdf()->create([
|
||||
'workspace_id' => (int) $otherTenant->workspace_id,
|
||||
'managed_environment_id' => (int) $otherTenant->getKey(),
|
||||
'source_environment_review_id' => (int) $pack->environment_review_id,
|
||||
'source_review_pack_id' => (int) $pack->getKey(),
|
||||
'operation_run_id' => (int) $run->getKey(),
|
||||
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
||||
'status' => StoredReport::STATUS_QUEUED,
|
||||
]);
|
||||
|
||||
StoredReport::factory()->managementReportPdf()->create([
|
||||
'workspace_id' => (int) $tenant->workspace_id,
|
||||
'managed_environment_id' => (int) $tenant->getKey(),
|
||||
'source_environment_review_id' => (int) $otherReview->getKey(),
|
||||
'source_review_pack_id' => (int) $otherPack->getKey(),
|
||||
'operation_run_id' => (int) $run->getKey(),
|
||||
'profile' => ReportProfileRegistry::CUSTOMER_EXECUTIVE,
|
||||
'status' => StoredReport::STATUS_QUEUED,
|
||||
]);
|
||||
|
||||
expect(app(ManagementReportPdfService::class)->findReportForRun($run))->toBeNull();
|
||||
});
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
# Requirements Checklist: Spec 412 - Pilot Readiness Remediation Pack
|
||||
|
||||
**Purpose**: Preparation readiness checklist for a bounded Spec 407 remediation package.
|
||||
**Created**: 2026-06-24
|
||||
**Feature**: `specs/412-pilot-readiness-remediation-pack/`
|
||||
|
||||
## Candidate Selection
|
||||
|
||||
- [x] The selected candidate was directly provided by the operator as the "Spec 408 - Pilot Readiness Remediation Pack" draft.
|
||||
- [x] The package records the numbering deviation because branches 408 through 411 are already reserved by existing local/remote work.
|
||||
- [x] `docs/product/spec-candidates.md` was reviewed and reports no safe automatic next-best-prep target.
|
||||
- [x] `docs/product/roadmap.md` was reviewed and supports management-report/customer-facing hardening as a near-term manual follow-through area.
|
||||
- [x] The selected candidate is not already present as `specs/412-pilot-readiness-remediation-pack/`.
|
||||
- [x] Related completed and historical specs are read-only context.
|
||||
- [x] The smallest viable slice is limited to four Spec 407 findings.
|
||||
- [x] Close alternatives are deferred instead of hidden inside this package.
|
||||
- [x] Candidate Selection Gate result: PASS WITH NUMBERING DEVIATION.
|
||||
|
||||
## Completed-Spec Guardrail
|
||||
|
||||
- [x] Spec 407 was checked as source context and not selected for rewrite.
|
||||
- [x] Specs 400-406 are treated as completed/validated/implementation-history context where applicable.
|
||||
- [x] No completed-spec close-out, validation results, completed task markers, smoke results, browser evidence, screenshots, or review language are removed or normalized.
|
||||
- [x] Existing branch/spec number 408 belongs to unrelated public website work on another branch and is not overwritten.
|
||||
|
||||
## Spec Completeness
|
||||
|
||||
- [x] Problem statement is clear and product-oriented.
|
||||
- [x] Business/product value is explicit.
|
||||
- [x] Primary users/operators are named.
|
||||
- [x] Scope fields cover routes/surfaces, ownership, RBAC, and leakage checks.
|
||||
- [x] Functional requirements are testable.
|
||||
- [x] Non-functional requirements cover security, reliability, auditability, performance, product safety, and test governance.
|
||||
- [x] User stories include independent tests and acceptance criteria.
|
||||
- [x] Edge cases are documented.
|
||||
- [x] Out-of-scope boundaries forbid broad rewrites, new surfaces, new product concepts, and full browser audit.
|
||||
- [x] Success criteria are measurable.
|
||||
- [x] Assumptions, risks, and open questions are explicit.
|
||||
|
||||
## Constitution / Spec Gate
|
||||
|
||||
- [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.
|
||||
- [x] No new persisted entity, table, enum, status family, abstraction, taxonomy, or UI framework is approved.
|
||||
- [x] The plan reuses existing report/PDF, OperationRun, RBAC, Filament, and provider/finding surfaces.
|
||||
- [x] Runtime implementation must stop if broader architecture or product decisions are required.
|
||||
|
||||
## Product Surface Contract
|
||||
|
||||
- [x] `docs/product/standards/product-surface-contract.md` is referenced.
|
||||
- [x] No-legacy posture is recorded.
|
||||
- [x] UI Surface Impact is concrete and does not claim no-impact.
|
||||
- [x] Product Surface Impact is completed for review/report, operations, finding, and provider no-access surfaces.
|
||||
- [x] Page archetypes, surface budgets, Technical Annex/deep-link demotion, and canonical vocabulary are recorded.
|
||||
- [x] UI Action Matrix is recorded for the changed operator-facing surfaces.
|
||||
- [x] Browser proof is required for rendered UI changes.
|
||||
- [x] Human Product Sanity is required.
|
||||
- [x] Product Surface exceptions are `none` for preparation.
|
||||
- [x] Implementation report close-out fields are required.
|
||||
|
||||
## Plan Completeness
|
||||
|
||||
- [x] Plan identifies PHP/Laravel/Filament/Livewire/Pest/PostgreSQL/Sail context.
|
||||
- [x] Plan names likely affected existing runtime surfaces without making code changes.
|
||||
- [x] Plan distinguishes remediation from broad audit or architecture rewrite.
|
||||
- [x] Plan includes UI/Product Surface, Filament/Livewire/deployment, RBAC, audit, evidence/result truth, OperationRun, provider boundary, and test-governance posture.
|
||||
- [x] Plan defines implementation phases, output strategy, stop conditions, and risk controls.
|
||||
- [x] Plan does not contradict repository architecture or current code truth.
|
||||
|
||||
## Task Completeness
|
||||
|
||||
- [x] Tasks are ordered by safety/inventory, reproduction, tests, implementation, browser proof, and close-out.
|
||||
- [x] Tasks are small and verifiable.
|
||||
- [x] Tasks include dirty-state checks before/after.
|
||||
- [x] Tasks include reproduction/validation of each Spec 407 finding before fixing.
|
||||
- [x] Tasks include targeted tests for report/PDF, operations, finding detail, and provider no-access behavior.
|
||||
- [x] Tasks include authenticated-provider-denial coverage so no-access clarity is measurable.
|
||||
- [x] Tasks include focused browser proof and explicitly forbid claiming a full browser audit.
|
||||
- [x] Tasks include Product Surface and Filament output-contract close-out fields.
|
||||
- [x] Tasks include explicit non-goals and stop conditions.
|
||||
|
||||
## Open Questions / Readiness
|
||||
|
||||
- [x] No open product question blocks starting implementation.
|
||||
- [x] Any non-reproducible finding must be documented during implementation rather than silently marked fixed.
|
||||
- [x] Required final report matrices are named.
|
||||
- [x] Spec Readiness Gate result: PASS.
|
||||
|
||||
## Review Outcome
|
||||
|
||||
- [x] Review outcome class: `acceptable-special-case` for a focused pilot-readiness remediation pack after a broad browser audit.
|
||||
- [x] Workflow outcome: `keep`.
|
||||
- [x] Final note location: `specs/412-pilot-readiness-remediation-pack/implementation-report.md` during implementation.
|
||||
- [x] No application implementation was performed during preparation.
|
||||
@ -0,0 +1,173 @@
|
||||
# Implementation Report: Spec 412 - Pilot Readiness Remediation Pack
|
||||
|
||||
## Summary
|
||||
|
||||
Spec 412 remediates the four included Spec 407 pilot-readiness findings without adding new routes, persisted entities, status families, navigation, report templates, or provider access models.
|
||||
|
||||
Implemented runtime changes:
|
||||
|
||||
- Finding detail demotes raw fingerprints, scope keys, source fingerprints, subject external IDs, detector/control/provider keys, provider object type, and run IDs into a collapsed `Technical identifiers` section.
|
||||
- Sanitized evidence JSON is collapsed by default on finding detail.
|
||||
- Provider connection member-missing-capability access now redirects to a clearer provider no-access outcome while preserving deny-as-not-found for non-members and cross-workspace actors.
|
||||
- The no-access page now renders provider-specific copy for provider-connection permission denials.
|
||||
- `report.management.generate` is classified in the existing OperationRun actionability registry as an artifact-producing operation.
|
||||
- `report.management.generate` terminal follow-up now resolves against the existing readable ready management PDF truth for `StoredReport` artifacts in the same workspace/environment and review-pack/review scope.
|
||||
- Management PDF `StoredReport` lookups are now scoped consistently by workspace, managed environment, review pack, and source environment review for ready/active/retry states, and by operation-run workspace/environment/source context for run-bound lookup.
|
||||
|
||||
No ReviewPack page, signed download controller, Operations page, or TenantlessOperationRunViewer runtime change was required; focused tests and browser proof show the existing behavior satisfies the included operations finding while the management PDF source truth is now explicitly service-enforced.
|
||||
|
||||
## Spec 407 Finding Remediation Matrix
|
||||
|
||||
| Finding | Remediation | Evidence | Result |
|
||||
|---|---|---|---|
|
||||
| Ready management PDFs not surfaced coherently | Existing `ViewReviewPack` ready-PDF precedence was verified and `ManagementReportPdfService` was hardened so only same-scope ready PDFs can drive the Review Pack ready/download state. Ready PDFs render `Download management PDF`; generate is not primary when a valid same-scope ready PDF exists. | `Spec379ManagementReportPdfTest`, `Spec404ManagementReportPdfRuntimeValidationTest`, `ReviewPackDownloadTest`, `Spec379ManagementReportPdfSmokeTest` | Fixed |
|
||||
| Operations index/detail browser navigation timeout | Operations index/detail render DB-only, preserve canonical links, and focused browser paths complete without JS/console errors. | `OperationsCanonicalUrlsTest`, `OperationsDbOnlyRenderTest`, `OperationsHubProductizationTest`, `Spec391OperationsHubStabilityTest`, `TenantlessOperationRunViewerTest`, `Spec391...SmokeTest`, `Spec360...SmokeTest` | Remediated by proof; no runtime operation page change required |
|
||||
| Finding detail exposes raw internal hashes by default | Raw technical identifiers moved into collapsed technical detail; default body remains human-readable. | `DriftFindingDetailTest`, `Spec412PilotReadinessRemediationSmokeTest` | Fixed |
|
||||
| Readonly/provider-connection no-access outcome confusing/login-like | Member-missing-`PROVIDER_VIEW` branch redirects to provider-specific no-access copy; non-members/cross-workspace stay non-leaky. | `CapabilityForbiddenTest`, `ProviderConnectionAuthorizationTest`, `ProviderConnectionsUiEnforcementTest`, `TenantlessListRouteTest`, `Spec412PilotReadinessRemediationSmokeTest` | Fixed |
|
||||
|
||||
## Report/PDF State Matrix
|
||||
|
||||
| State | Behavior | Evidence |
|
||||
|---|---|---|
|
||||
| Ready management PDF | Review pack detail shows `Download management PDF` and does not show `Generate management PDF` as the primary state when a readable PDF belongs to the same workspace/environment/review scope. | `Spec379ManagementReportPdfTest`, `Spec379ManagementReportPdfSmokeTest` |
|
||||
| Missing/unavailable/blocked PDF | Review pack detail shows unavailable/generate-safe state and avoids serving invalid ready output. | `Spec379ManagementReportPdfTest`, `Spec404ManagementReportPdfRuntimeValidationTest`, `Spec379ManagementReportPdfSmokeTest` |
|
||||
| Authorized signed download | Signed management PDF and review pack download routes work for entitled actors. | `Spec379ManagementReportPdfTest`, `ReviewPackDownloadTest` |
|
||||
| Unauthorized/cross-workspace/unsigned download | Direct unsigned, expired, invalid, non-member, and cross-workspace report/download routes remain blocked. | `Spec379ManagementReportPdfTest`, `Spec404ManagementReportPdfRuntimeValidationTest`, `ReviewPackDownloadTest` |
|
||||
|
||||
Browser proof validates the rendered PDF action states. Binary stream authorization remains covered by feature tests because the browser harness does not provide reliable assertions for streamed download bodies.
|
||||
|
||||
## Product Surface Contract
|
||||
|
||||
- No-legacy posture: canonical correction only; no compatibility alias or duplicate UI introduced.
|
||||
- Product Surface Impact: existing review/report, operations, finding detail, and provider no-access surfaces only.
|
||||
- UI Surface Impact: existing pages changed; no new navigation, major pages, modals, tables, or status families.
|
||||
- Page archetypes: review pack report/receipt, operations technical annex/receipt, finding decision/secondary context, provider no-access settings denial outcome.
|
||||
- Surface budgets: pass for focused surfaces. Visible complexity decreased on finding/provider surfaces and stayed neutral for operations/report surfaces.
|
||||
- Technical Annex / deep-link demotion: fingerprints, scope keys, source identifiers, provider keys, run IDs, and sanitized evidence payloads are collapsed or secondary by default.
|
||||
- Canonical status vocabulary: no new vocabulary introduced.
|
||||
- Product Surface exceptions: none.
|
||||
- Human Product Sanity: pass. The affected surfaces keep a clear primary operator question, avoid contradictory PDF action state, keep operations diagnostic depth off the first decision path, demote finding internals, and avoid login-like provider-denial copy.
|
||||
- Completed-spec rewrite assertion: no completed Specs 400-407 were edited.
|
||||
|
||||
## UI Action Matrix Close-Out
|
||||
|
||||
- Review pack management PDF: no new actions. Existing generate action remains `->action(...)`, confirmed, authorized, audited, and OperationRun-backed. Ready PDF download remains URL-based signed download.
|
||||
- Operations: no new actions. Existing inspect/drilldown paths and `OperationRunLinks` are preserved.
|
||||
- Finding detail: no new actions. Technical identifiers and sanitized evidence are collapsed detail, not first-decision content.
|
||||
- Provider connection no-access: no new action. Provider detail/manage/run actions remain capability-gated; non-member/cross-workspace record existence stays hidden.
|
||||
|
||||
## Filament / Livewire / Assets / Search
|
||||
|
||||
- Livewire v4 compliance: confirmed on Livewire 4.1.4.
|
||||
- Provider registration: unchanged; Laravel 12 panel providers remain registered through `apps/platform/bootstrap/providers.php`.
|
||||
- Global search: no resource was made globally searchable. `ProviderConnectionResource` remains `protected static bool $isGloballySearchable = false`.
|
||||
- Destructive/high-impact actions: no new destructive action. Existing provider and management PDF high-impact actions retain confirmation and authorization.
|
||||
- Asset strategy: no assets added or changed; no new `filament:assets` deployment impact from this spec.
|
||||
|
||||
## Browser Proof
|
||||
|
||||
Focused browser command:
|
||||
|
||||
```bash
|
||||
cd apps/platform && ./vendor/bin/sail artisan test tests/Browser/Spec412PilotReadinessRemediationSmokeTest.php tests/Browser/Spec379ManagementReportPdfSmokeTest.php tests/Browser/Spec391OperationsHubStabilitySmokeTest.php tests/Browser/Spec360OperationRunCanonicalCutoverSmokeTest.php
|
||||
```
|
||||
|
||||
Result: pass, 5 tests, 75 assertions.
|
||||
|
||||
Paths/states covered:
|
||||
|
||||
- Review pack ready and unavailable management PDF action states.
|
||||
- Operations Hub index load with Livewire/Alpine present and no debug/asset failure signatures.
|
||||
- OperationRun detail load for canonical reconciled and stale queued runs.
|
||||
- Finding detail default view with raw hash values absent and technical sections collapsed.
|
||||
- Authenticated provider no-access copy for provider-connection permission denial.
|
||||
|
||||
All focused browser checks asserted no JavaScript errors and no console logs.
|
||||
|
||||
## Automated Validation
|
||||
|
||||
Passing focused suite:
|
||||
|
||||
```bash
|
||||
cd apps/platform && ./vendor/bin/sail artisan test --filter='Spec379ManagementReportPdfTest|Spec404ManagementReportPdfRuntimeValidationTest|ReviewPackDownloadTest|DriftFindingDetailTest|OperationsHubProductizationTest|TenantlessOperationRunViewerTest|OperationsCanonicalUrlsTest|OperationsDbOnlyRenderTest|Spec391OperationsHubStabilityTest|ProviderConnectionAuthorizationTest|CapabilityForbiddenTest|ProviderConnectionsUiEnforcementTest|TenantlessListRouteTest|Spec367OperationRunActionabilityRegistryTest|Spec367OperationRunActionabilityResolverTest'
|
||||
```
|
||||
|
||||
Result: pass, 131 tests, 871 assertions.
|
||||
|
||||
Additional pre-merge hardening after final manual review:
|
||||
|
||||
```bash
|
||||
cd apps/platform && ./vendor/bin/sail artisan test --filter='Spec367OperationRunActionability'
|
||||
cd apps/platform && ./vendor/bin/sail artisan test --filter='Spec379ManagementReportPdfTest|Spec404ManagementReportPdfRuntimeValidationTest|ReviewPackDownloadTest|DriftFindingDetailTest|OperationsHubProductizationTest|TenantlessOperationRunViewerTest|OperationsCanonicalUrlsTest|OperationsDbOnlyRenderTest|Spec391OperationsHubStabilityTest|ProviderConnectionAuthorizationTest|CapabilityForbiddenTest|ProviderConnectionsUiEnforcementTest|TenantlessListRouteTest|Spec367OperationRunActionabilityRegistryTest|Spec367OperationRunActionabilityResolverTest'
|
||||
cd apps/platform && ./vendor/bin/sail artisan test --filter='Spec379ManagementReportPdfTest|Spec404ManagementReportPdfRuntimeValidationTest'
|
||||
```
|
||||
|
||||
Results: pass, 18 tests / 132 assertions for the Actionability family, pass, 131 tests / 871 assertions for the expanded focused Spec 412 suite, and pass, 26 tests / 193 assertions for the management PDF family.
|
||||
|
||||
Other passing checks:
|
||||
|
||||
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` -> pass.
|
||||
- `git diff --check` -> pass.
|
||||
|
||||
Broad validation filters were run as requested. They exposed residual failures outside the Spec 412 touched surfaces:
|
||||
|
||||
- `--filter=ReviewPack`: failed in existing customer-output/rendered-report/review-pack generation expectations; in-scope Spec379/392/404 and download coverage passed.
|
||||
- `--filter=ManagementReport`: Spec379/404 passed; existing Spec366 rendered-report browser path returned 404.
|
||||
- `--filter=OperationRun`: exposed an in-scope actionability registry gap for `report.management.generate`; fixed and verified. Final manual review also hardened ready management PDF artifact resolution for the same operation family, and repeat review hardened the underlying management PDF service lookups. Other residual failures remain outside the focused remediation.
|
||||
- `--filter=Operations`: focused operations DB-only, canonical URL, Operations Hub, Spec391, and browser proof passed; broader dashboard/lifecycle/provider-operation contracts still fail.
|
||||
- `--filter=Finding`: focused finding detail and Spec412 browser proof passed; broader baseline/governance/dashboard residuals still fail.
|
||||
- `--filter=ProviderConnection`: focused provider no-access, authorization, scope, and UI enforcement passed; older Spec394 provider freshness and Spec281 onboarding smoke residuals still fail.
|
||||
|
||||
Full `cd apps/platform && ./vendor/bin/sail artisan test` was not run because the broader filters already show unrelated residual failures.
|
||||
|
||||
## Post-Implementation Analysis
|
||||
|
||||
Confirmed in-scope findings found during the loop:
|
||||
|
||||
- `report.management.generate` was present in `OperationCatalog` and produced by management PDF generation, but was missing from `OperationRunActionabilityRegistry`.
|
||||
- Remediation: classify it under the existing `artifact_or_later_success_v1` policy with review-pack/review context keys.
|
||||
- Final manual review found that the registry classification needed a concrete `StoredReport` artifact resolver branch so failed `report.management.generate` runs can be resolved by same-scope ready management PDFs without waiting for a later successful run.
|
||||
- Remediation: add ready management PDF artifact lookup to `OperationRunActionabilityResolver` through `ManagementReportPdfService::findReadyReport()` so current-follow-up resolution uses the same readable PDF/file-size/SHA/PDF-byte validation as the review-pack surface and signed download route, with an explicit workspace/environment match on the resolving `StoredReport`.
|
||||
- Repeat final review found that `ManagementReportPdfService` still allowed cross-scope `StoredReport` rows sharing the same `source_review_pack_id` to influence ready, active, retry, or run-bound PDF decisions before downstream signed-download authorization rejected them.
|
||||
- Remediation: bind management PDF ready/active/retry lookups to workspace, managed environment, review pack, and source environment review; bind run-bound lookup to the operation run workspace, managed environment, and source context.
|
||||
- Verification: `Spec367OperationRunActionabilityRegistryTest`, `Spec367OperationRunActionabilityResolverTest`, the Actionability family, the management PDF family, the new Spec412 management PDF scope guards, focused browser proof, and the expanded focused Spec 412 suite passed.
|
||||
|
||||
Remaining in-scope findings: none.
|
||||
|
||||
Residual risks:
|
||||
|
||||
- Broad validation filters still have unrelated failures from older productization/dashboard/review-output specs. They are not caused by changed files in this implementation and are documented above.
|
||||
- Browser proof for streamed management PDF binary body is represented by rendered download action state plus feature-route assertions, not a browser download-body assertion.
|
||||
|
||||
## Deployment Impact
|
||||
|
||||
- Migrations: none.
|
||||
- Env vars: none.
|
||||
- Queue/cron topology: none.
|
||||
- Storage/volumes: none.
|
||||
- Filament assets: none.
|
||||
- Staging/Dokploy: no new deployment step. Existing management PDF storage/queue/runtime requirements remain unchanged.
|
||||
|
||||
## Final Status
|
||||
|
||||
Final working tree status recorded after implementation:
|
||||
|
||||
```text
|
||||
## 412-pilot-readiness-remediation-pack
|
||||
M apps/platform/app/Filament/Pages/NoAccess.php
|
||||
M apps/platform/app/Filament/Resources/FindingResource.php
|
||||
M apps/platform/app/Filament/Resources/ProviderConnectionResource.php
|
||||
M apps/platform/app/Policies/ProviderConnectionPolicy.php
|
||||
M apps/platform/app/Services/ReviewPacks/ManagementReportPdfService.php
|
||||
M apps/platform/app/Support/Operations/Actionability/OperationRunActionabilityRegistry.php
|
||||
M apps/platform/app/Support/Operations/Actionability/OperationRunActionabilityResolver.php
|
||||
M apps/platform/resources/views/filament/pages/no-access.blade.php
|
||||
M apps/platform/tests/Feature/Drift/DriftFindingDetailTest.php
|
||||
M apps/platform/tests/Feature/Operations/Spec367OperationRunActionabilityResolverTest.php
|
||||
M apps/platform/tests/Feature/ProviderConnections/CapabilityForbiddenTest.php
|
||||
M apps/platform/tests/Feature/ReviewPack/Spec379ManagementReportPdfTest.php
|
||||
?? apps/platform/tests/Browser/Spec412PilotReadinessRemediationSmokeTest.php
|
||||
?? specs/412-pilot-readiness-remediation-pack/
|
||||
```
|
||||
|
||||
Merge Readiness Gate: passed for the active Spec 412 focused scope, with documented unrelated broad-lane residual failures.
|
||||
239
specs/412-pilot-readiness-remediation-pack/plan.md
Normal file
239
specs/412-pilot-readiness-remediation-pack/plan.md
Normal file
@ -0,0 +1,239 @@
|
||||
# Implementation Plan: Spec 412 - Pilot Readiness Remediation Pack
|
||||
|
||||
**Branch**: `412-pilot-readiness-remediation-pack` | **Date**: 2026-06-24 | **Spec**: `specs/412-pilot-readiness-remediation-pack/spec.md`
|
||||
**Input**: Feature specification from `specs/412-pilot-readiness-remediation-pack/spec.md`
|
||||
|
||||
## Summary
|
||||
|
||||
Prepare a bounded remediation implementation over four Spec 407 pilot-readiness findings. The future implementation must fix ready management PDF surfacing, prove operations routes complete browser navigation, demote raw finding hashes from default detail content, and clarify readonly provider-connection no-access outcomes. It must reuse existing Laravel/Filament/Livewire/report/OperationRun/RBAC surfaces, add focused Pest and browser proof, produce an implementation report with the required matrices, and avoid new surfaces, new product concepts, broad architecture, or completed-spec rewrites.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: PHP 8.4.15, Laravel 12.52.0
|
||||
**Primary Dependencies**: Filament 5.2.1, Livewire 4.1.4, Pest 4.3.1, Laravel Sail
|
||||
**Storage**: PostgreSQL; no new schema expected
|
||||
**Testing**: Pest feature/Filament/Livewire tests plus focused browser smoke
|
||||
**Validation Lanes**: fast-feedback, confidence, browser; PostgreSQL only if implementation unexpectedly changes PostgreSQL-specific database behavior
|
||||
**Target Platform**: Laravel monolith under `apps/platform`
|
||||
**Project Type**: web application with Filament admin/system panels
|
||||
**Performance Goals**: operations pages render with bounded DB-only work and no browser navigation timeout under focused local audit conditions
|
||||
**Constraints**: no new product surface, no full browser audit, no staging/Dokploy validation, no completed-spec rewrite, no Graph/external calls during render
|
||||
**Scale/Scope**: existing review/report, operations, finding, and provider-connection surfaces only
|
||||
|
||||
## Repository Surfaces Likely Affected
|
||||
|
||||
Implementation must verify exact file ownership before editing, but current repo inventory identifies these likely surfaces:
|
||||
|
||||
- `apps/platform/app/Filament/Resources/ReviewPackResource.php`
|
||||
- `apps/platform/app/Filament/Resources/ReviewPackResource/Pages/ViewReviewPack.php`
|
||||
- `apps/platform/app/Services/ReviewPacks/ManagementReportPdfService.php`
|
||||
- `apps/platform/app/Http/Controllers/ManagementReportPdfDownloadController.php`
|
||||
- `apps/platform/app/Http/Controllers/ReviewPackDownloadController.php`
|
||||
- `apps/platform/app/Http/Controllers/ReviewPackRenderedReportController.php`
|
||||
- `apps/platform/app/Filament/Pages/Monitoring/Operations.php`
|
||||
- `apps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php`
|
||||
- `apps/platform/resources/views/filament/pages/monitoring/operations.blade.php`
|
||||
- `apps/platform/resources/views/filament/pages/operations/tenantless-operation-run-viewer.blade.php`
|
||||
- `apps/platform/app/Filament/Resources/FindingResource.php`
|
||||
- `apps/platform/app/Filament/Resources/ProviderConnectionResource.php`
|
||||
- route and middleware behavior in `apps/platform/routes/web.php` and related workspace/provider auth middleware only if inventory proves the no-access issue lives there
|
||||
- existing tests under `apps/platform/tests/Feature/ReviewPack/`, `apps/platform/tests/Feature/Monitoring/`, `apps/platform/tests/Feature/Operations/`, `apps/platform/tests/Feature/Filament/`, `apps/platform/tests/Feature/ProviderConnections/`, and finding-focused tests
|
||||
|
||||
No runtime file outside these related surfaces should be changed without updating this plan first.
|
||||
|
||||
## UI / Surface Guardrail Plan
|
||||
|
||||
- **Guardrail scope**: changed existing surfaces only.
|
||||
- **Affected routes/pages/actions/states/navigation/panel/provider surfaces**: review pack detail management PDF action state, signed report/PDF downloads, operations index/detail, finding detail default body, provider connection no-access outcome.
|
||||
- **No-impact class, if applicable**: N/A.
|
||||
- **Native vs custom classification summary**: native Filament plus existing shared primitives; avoid custom UI framework/styling.
|
||||
- **Shared-family relevance**: reports/downloads, status messaging, technical detail disclosure, OperationRun links, authorization/no-access messaging.
|
||||
- **State layers in scope**: detail action state, signed route state, page load/readiness, default-vs-technical detail disclosure, route/authorization outcome.
|
||||
- **Audience modes in scope**: operator-MSP, readonly/customer-safe where exposed, support-platform for technical detail.
|
||||
- **Decision/diagnostic/raw hierarchy plan**: product decision first; diagnostics second; support/raw evidence collapsed or gated.
|
||||
- **Raw/support gating plan**: demote hashes and raw identifiers into collapsed/support/operator-only technical detail if retained.
|
||||
- **One-primary-action / duplicate-truth control**: ready PDF produces one ready/download/open action; generate is not primary when a valid ready PDF exists.
|
||||
- **UI Action Matrix handling**: use the spec's UI Action Matrix as the action-placement source for header, row, bulk, empty-state, primary, secondary, destructive, and technical/audit actions across the four affected surfaces.
|
||||
- **Handling modes by drift class or surface**: review-mandatory for all four included surfaces; exception-required for any Product Surface budget violation.
|
||||
- **Repository-signal treatment**: report-only unless implementation discovers broad architecture mismatch, in which case stop and update spec/plan.
|
||||
- **Special surface test profiles**: shared-detail-family, monitoring-state-page, exception-coded-surface.
|
||||
- **Required tests or manual smoke**: feature/Filament/Livewire tests plus focused browser proof.
|
||||
- **Exception path and spread control**: none approved.
|
||||
- **Active feature PR close-out entry**: Product Surface / Focused Browser Proof / Pilot Readiness Remediation.
|
||||
- **UI/Productization coverage decision**: no new reachable surface; existing surface behavior changed and proof captured in implementation report.
|
||||
- **Coverage artifacts to update**: none expected because no new surface is introduced; update docs/ui-ux-enterprise-audit only if implementation materially changes route inventory, page archetype, navigation, or page structure beyond the approved remediation.
|
||||
- **No-impact rationale**: N/A.
|
||||
- **Navigation / Filament provider-panel handling**: no panel provider or navigation change expected.
|
||||
- **Screenshot or page-report need**: focused screenshots/browser records required; full page reports are not required.
|
||||
|
||||
## Product Surface Contract Plan
|
||||
|
||||
- **Product Surface Contract reference**: `docs/product/standards/product-surface-contract.md`.
|
||||
- **No-legacy posture**: canonical correction; no compatibility aliases, fallback readers, duplicate UI, or old labels unless the spec is updated with an explicit exception.
|
||||
- **Page archetype and surface budget plan**:
|
||||
- Review pack detail: Report/Receipt; one primary report/PDF action based on truth.
|
||||
- Operations index/detail: Technical Annex/Receipt; browser readiness and no raw diagnostics by default.
|
||||
- Finding detail: Decision/Secondary Context; raw hashes demoted.
|
||||
- Provider no-access: Settings denial outcome; clear and non-leaky.
|
||||
- **Technical Annex and deep-link demotion plan**: OperationRun proof links, evidence IDs, fingerprints, source hashes, payloads, logs, and raw provider fields must not become default product content.
|
||||
- **Canonical status vocabulary plan**: map report/readiness/status labels to allowed vocabulary (`Ready`, `Needs attention`, `Blocked`, `Running`, `Failed`, `Expired`, `Not configured`, `Unknown`, `Historical`, `Superseded`) or existing badge domains that already map to it.
|
||||
- **Product Surface exceptions**: none approved.
|
||||
- **Browser verification plan**: focused browser paths listed in `spec.md`.
|
||||
- **Human Product Sanity plan**: record result in `implementation-report.md`.
|
||||
- **Visible complexity outcome target**: decreased for review/finding/provider; neutral for operations.
|
||||
- **Implementation report target**: `specs/412-pilot-readiness-remediation-pack/implementation-report.md`.
|
||||
|
||||
## Filament / Livewire / Deployment Posture
|
||||
|
||||
- **Livewire v4 compliance**: Livewire 4.1.4 confirmed through Laravel Boost.
|
||||
- **Panel provider registration location**: existing providers are registered in `apps/platform/bootstrap/providers.php`; no panel provider change expected.
|
||||
- **Global search posture**: no resource should be newly globally searchable. Existing sensitive resources such as ReviewPackResource and ProviderConnectionResource keep global search disabled where applicable. If FindingResource global search is touched, it must have safe View/Edit coverage and tenant scoping or remain disabled according to repo rules.
|
||||
- **Destructive/high-impact action posture**: no new destructive action. Existing management PDF generation is high-impact/queued and must keep `->action(...)`, confirmation, authorization, audit/OperationRun behavior, and tests.
|
||||
- **Asset strategy**: no new assets expected; `filament:assets` is not required unless implementation registers new Filament assets, which is out of scope.
|
||||
- **Testing plan**: Pest feature/Filament/Livewire tests for report/PDF state, signed download authorization, operations render/readiness, finding default body hash demotion, provider no-access semantics, plus focused browser smoke.
|
||||
- **Deployment impact**: no env vars, migrations, scheduler, storage volumes, or queue topology changes expected. Existing queue/storage for management PDF remains relevant.
|
||||
|
||||
## Shared Pattern & System Fit
|
||||
|
||||
- **Cross-cutting feature marker**: yes.
|
||||
- **Systems touched**: report state/action links, signed downloads, OperationRun links/readiness, finding technical disclosure, provider authorization copy.
|
||||
- **Shared abstractions reused**: `ManagementReportPdfService`, `OperationUxPresenter`, `OperationRunLinks`, `BadgeRenderer`, `UiEnforcement` / `WorkspaceUiEnforcement`, existing policies/middleware/controllers.
|
||||
- **New abstraction introduced? why?**: none planned. A small local helper is acceptable only if it replaces repeated state checks in an existing surface and does not become a new framework.
|
||||
- **Why the existing abstraction was sufficient or insufficient**: the repo already has report/PDF services, OperationRun helpers, badge domains, and RBAC helpers; defects are state selection/proof issues.
|
||||
- **Bounded deviation / spread control**: none approved.
|
||||
|
||||
## OperationRun UX Impact
|
||||
|
||||
- **Touches OperationRun start/completion/link UX?**: yes, only existing management PDF generation run links/toasts and operations route readiness.
|
||||
- **Central contract reused**: shared OperationRun UX layer, `OperationUxPresenter`, `OperationRunLinks`, `OperationRunService`.
|
||||
- **Delegated UX behaviors**: queued toast, open/view run link, artifact link, browser event, dedupe/blocked messaging, tenant/workspace-safe URL resolution remain delegated.
|
||||
- **Surface-owned behavior kept local**: local action visibility and initiation input only.
|
||||
- **Queued DB-notification policy**: no new opt-in.
|
||||
- **Terminal notification path**: central lifecycle mechanism.
|
||||
- **Exception path**: none.
|
||||
|
||||
## Provider Boundary & Portability Fit
|
||||
|
||||
- **Shared provider/platform boundary touched?**: yes, bounded.
|
||||
- **Provider-owned seams**: provider-connection detail and provider-specific readiness/diagnostics.
|
||||
- **Platform-core seams**: workspace/environment membership, capability denial, no-access route outcome.
|
||||
- **Neutral platform terms / contracts preserved**: workspace, managed environment, provider connection, access, permission, membership.
|
||||
- **Retained provider-specific semantics and why**: Microsoft/provider labels remain only where existing provider connection UI already owns them.
|
||||
- **Bounded extraction or follow-up path**: none expected; broader provider readiness remains a separate manual promotion item.
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before implementation. Re-check after any scope change.*
|
||||
|
||||
- Inventory-first: no inventory truth changes expected.
|
||||
- Read/write separation: report generation remains explicit/queued/confirmed; no new write action.
|
||||
- Graph contract path: no Graph call during render; any existing Graph work stays behind services/jobs.
|
||||
- Deterministic capabilities: no new capability registry entry expected; if one becomes necessary, update spec/plan first.
|
||||
- RBAC-UX: non-member workspace/environment access remains 404/deny-as-not-found; member missing capability remains 403; UI visibility is not security.
|
||||
- Workspace isolation: report, operation, finding, and provider records remain workspace/environment scoped.
|
||||
- Tenant isolation: managed-environment-scoped reads/downloads remain entitled and non-leaky.
|
||||
- Run observability: existing management PDF generation run remains observable through OperationRun; no new operation type planned.
|
||||
- OperationRun start UX: no local queued toast/link/event composition.
|
||||
- Ops-UX 3-surface feedback: no new DB notification policy.
|
||||
- Ops-UX lifecycle: no direct status/outcome transition outside `OperationRunService`.
|
||||
- Summary counts contract: no new summary count keys expected.
|
||||
- Data minimization: no secrets, tokens, raw provider payloads, file paths, stack traces, fingerprints, or source hashes in customer/default content.
|
||||
- Test governance: lane classification and focused browser proof are explicit.
|
||||
- Proportionality: no new source of truth, entity, status family, taxonomy, or framework.
|
||||
- Product Surface Contract: no raw technical identifiers in default product views; one dominant next action per affected decision context.
|
||||
- Completed-spec guardrail: Specs 400-407 remain read-only context.
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1 - Focused Inventory And Reproduction
|
||||
|
||||
Confirm dirty state, route list, relevant files, existing tests, and whether each Spec 407 finding reproduces locally. Document non-reproducible findings without expanding scope.
|
||||
|
||||
### Phase 2 - Report/PDF State Remediation
|
||||
|
||||
Add focused tests first for ready, missing, failed, inconsistent, unauthorized, cross-workspace, signed, and unsigned report/PDF states. Fix only existing report/PDF state selection and action presentation so ready stored management PDFs surface as ready/downloadable.
|
||||
|
||||
### Phase 3 - Operations Route Load Completion
|
||||
|
||||
Add/adjust tests around operations index/detail render, DB-only behavior, query bounds, and route readiness. Investigate browser/network behavior and fix only page behavior causing timeout, 500, Livewire fatal error, blocking request, heavy payload, or broken readiness signal.
|
||||
|
||||
### Phase 4 - Finding Hash Demotion
|
||||
|
||||
Add default-body tests proving raw hashes are not prominent. Move fingerprints/scope/source hashes into existing technical/collapsed/support/operator detail if support workflows still need them.
|
||||
|
||||
### Phase 5 - Readonly Provider No-Access Clarity
|
||||
|
||||
Add/adjust authorization tests for readonly, unauthorized, and cross-workspace actors. Improve copy/outcome without granting access or leaking record existence.
|
||||
|
||||
### Phase 6 - Focused Browser Proof And Close-Out
|
||||
|
||||
Run focused browser paths, inspect console/network/runtime logs, complete required matrices, run targeted tests, and produce implementation report. Do not run or claim a full browser audit.
|
||||
|
||||
## Data / Migration Plan
|
||||
|
||||
- No migration is expected.
|
||||
- No new persisted entity, enum, state, or report artifact is approved.
|
||||
- Existing `StoredReport`, `ReviewPack`, `OperationRun`, `Finding`, and `ProviderConnection` truth must be reused.
|
||||
- If implementation discovers missing persisted truth is required, stop and update spec/plan before coding.
|
||||
|
||||
## RBAC / Authorization Plan
|
||||
|
||||
- Preserve signed route authorization for report/PDF downloads.
|
||||
- Preserve workspace and managed-environment entitlement checks for review packs, operation runs, findings, and provider connections.
|
||||
- Prove unauthorized and cross-workspace direct URLs remain blocked.
|
||||
- Prove readonly provider access remains denied while the outcome is clearer.
|
||||
- Do not replace server-side authorization with UI hiding.
|
||||
|
||||
## Audit / Observability Plan
|
||||
|
||||
- No new audit family expected.
|
||||
- Existing management PDF generation/download audit behavior must remain intact.
|
||||
- If a remediation changes high-impact action behavior, update existing audit tests or add targeted ones.
|
||||
- Operations pages remain DB-only at render time and continue to use `OperationRun` as execution truth.
|
||||
|
||||
## Test Strategy
|
||||
|
||||
- **Review/PDF**: add/update tests under `apps/platform/tests/Feature/ReviewPack/`, likely building on `Spec379ManagementReportPdfTest`, `Spec404ManagementReportPdfRuntimeValidationTest`, `ReviewPackDownloadTest`, and customer output route gate tests.
|
||||
- **Operations**: add/update tests under `apps/platform/tests/Feature/Monitoring/`, `apps/platform/tests/Feature/Operations/`, and `apps/platform/tests/Feature/Filament/` for render, DB-only, and route readiness.
|
||||
- **Finding detail**: add/update finding resource/render tests under the existing finding/Filament test families.
|
||||
- **Provider no-access**: add/update tests under `apps/platform/tests/Feature/ProviderConnections/` for readonly/unauthorized/cross-workspace behavior.
|
||||
- **Browser**: focused smoke only for the eight paths named in the spec.
|
||||
- **Final command set**: targeted filters first, then the smallest broader relevant suite. Full suite is useful but not a substitute for focused proof.
|
||||
|
||||
## Rollout Considerations
|
||||
|
||||
- No environment variable change expected.
|
||||
- No migration expected.
|
||||
- No queue topology change expected.
|
||||
- Existing management PDF generation queue/storage behavior remains relevant.
|
||||
- No `filament:assets` deployment step is introduced unless the implementation unexpectedly registers assets, which is out of scope.
|
||||
- Staging/Dokploy runtime validation remains outside this pack.
|
||||
|
||||
## Risk Controls
|
||||
|
||||
- Reproduce or validate each finding before fixing.
|
||||
- Keep fixes local to existing surfaces and services.
|
||||
- Add negative authorization tests before changing report/provider behavior.
|
||||
- Treat non-member and cross-workspace cases as leakage risks.
|
||||
- Browser proof must record route, actor, state, expected result, actual result, console/runtime/network outcome, and screenshots/log references where available.
|
||||
- If any P0 is discovered, stop broadening and report it; do not hide it inside local remediation.
|
||||
|
||||
## Candidate Selection Gate
|
||||
|
||||
**PASS WITH NUMBERING DEVIATION**. User directly supplied the candidate; it is bounded, roadmap-aligned, and not covered by an existing spec in this branch. Existing branch/spec numbering from other branches reserves 408 through 411, so this package uses 412.
|
||||
|
||||
## Preparation Analyze Result
|
||||
|
||||
Manual preparation analysis is required because this repo exposes prompt/agent stubs for `speckit.analyze` but no local executable analyze command. The analysis must check:
|
||||
|
||||
- spec/plan/tasks/checklist consistency;
|
||||
- completion guardrail for Specs 400-407;
|
||||
- no application implementation;
|
||||
- four-finding scope only;
|
||||
- Product Surface Contract coverage;
|
||||
- RBAC, workspace/environment isolation, OperationRun, evidence/result truth, and browser proof coverage;
|
||||
- no new surface, concept, persisted truth, status family, or broad abstraction.
|
||||
|
||||
## Spec Readiness Gate
|
||||
|
||||
**PASS** when `spec.md`, `plan.md`, `tasks.md`, and `checklists/requirements.md` remain aligned after manual preparation analysis. No product question blocks later implementation.
|
||||
457
specs/412-pilot-readiness-remediation-pack/spec.md
Normal file
457
specs/412-pilot-readiness-remediation-pack/spec.md
Normal file
@ -0,0 +1,457 @@
|
||||
# Feature Specification: Spec 412 - Pilot Readiness Remediation Pack
|
||||
|
||||
**Feature Branch**: `412-pilot-readiness-remediation-pack`
|
||||
**Created**: 2026-06-24
|
||||
**Status**: Draft
|
||||
**Input**: User-provided "Spec 408 - Pilot Readiness Remediation Pack" draft from `/Users/ahmeddarrazi/.codex/attachments/3275d3b5-1314-4fca-8943-ba1e5a70030b/pasted-text.txt`, Spec 407 full browser/UX/runtime audit findings, `docs/product/spec-candidates.md`, `docs/product/roadmap.md`, and the Product Surface Contract.
|
||||
|
||||
## Preparation Note
|
||||
|
||||
- **Numbering deviation**: The supplied draft names Spec ID 408. Existing local/remote branch `408-review-evidence-decision` already owns a different Spec Kit package on another branch, and 409 through 411 are also reserved by existing branches. This package uses the first verified free number, 412, to avoid overwriting or duplicating existing work.
|
||||
- **Selected candidate**: Pilot Readiness Remediation Pack.
|
||||
- **Source location**: Direct user-provided draft in the current request; source finding set comes from Spec 407.
|
||||
- **Why selected**: `docs/product/spec-candidates.md` reports no safe automatic next-best-prep target, but the operator supplied a concrete manual follow-through candidate that closes the bounded P1/P2/P3 findings from Spec 407.
|
||||
- **Why close alternatives were deferred**:
|
||||
- `management-report-pdf-staging-runtime-validation` remains a staging/Dokploy runtime validation lane, not this local pilot-readiness remediation.
|
||||
- `governance-artifact-lifecycle-retention-runtime` is separate artifact lifecycle work and must not be hidden inside a report/UI remediation pack.
|
||||
- Provider readiness/onboarding productization is optional P2 roadmap work and broader than the readonly no-access clarity issue.
|
||||
- A new full browser audit is explicitly out of scope; Spec 412 requires focused browser proof only.
|
||||
- **Roadmap relationship**: supports near-term controlled pilot and customer-facing hardening by removing concrete browser-audit exclusions after Spec 407.
|
||||
- **Completed-spec guardrail**:
|
||||
- Spec 407 exists as a preparation package with checked readiness checklist history and is used only as source context.
|
||||
- Specs 400-406 carry implementation/validation/close-out history and remain read-only context.
|
||||
- No completed spec may be rewritten, normalized, unchecked, or stripped of validation, smoke, browser, screenshots, or review evidence.
|
||||
- **Smallest viable implementation slice**: remediate only these four Spec 407 findings on existing surfaces: management PDF ready/download surfacing, operations route load completion, finding internal hash demotion, and readonly provider-connection no-access clarity.
|
||||
|
||||
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
|
||||
|
||||
- **Problem**: Spec 407 found that TenantPilot can enter a controlled pilot only with exclusions because ready management PDFs are not surfaced coherently, operations routes timed out in browser navigation, finding detail exposes raw internal hashes by default, and readonly provider-connection denial copy is confusing.
|
||||
- **Today's failure**: Operators can see contradictory report/PDF states, browser audit cannot reliably complete operations navigation, default finding detail includes technical identifiers that look like product content, and an authenticated readonly actor can see a login-style/no-membership outcome instead of a clear no-access result.
|
||||
- **User-visible improvement**: Pilot operators see ready/download states for existing management PDFs, can navigate operations pages without browser timeouts, get human-readable finding detail by default, and receive clearer access-denied outcomes without weakened authorization.
|
||||
- **Smallest enterprise-capable version**: One remediation pack over existing review/report, operations, finding detail, and provider-connection no-access surfaces with targeted Pest coverage and focused browser proof.
|
||||
- **Explicit non-goals**: No new report template, PDF renderer, PDF lifecycle architecture, report workflow, review-pack concept, customer review surface, operations dashboard, OperationRun architecture, finding taxonomy, provider onboarding flow, legal hold, purge/export governance, staging/Dokploy validation, JSONB migration, full browser audit, commercial lifecycle, or support desk integration.
|
||||
- **Permanent complexity imported**: No new persisted entities, status families, enums, source-of-truth objects, broad abstractions, navigation branches, or product concepts are approved. The future implementation may add focused tests and small local helpers only if they replace duplicated logic or keep existing truth readable.
|
||||
- **Why now**: Spec 407 returned `PASS WITH CONDITIONS` for controlled pilot and `customer-facing hardening: no` until these bounded findings are addressed or explicitly accepted.
|
||||
- **Why not local**: The issues span existing report/PDF state truth, browser-route readiness, customer/operator disclosure, and authorization UX; the pack needs one accountable remediation report rather than unrelated local fixes.
|
||||
- **Approval class**: Core Enterprise.
|
||||
- **Red flags triggered**: UI surface changes, report/download authorization risk, customer-safe boundary risk, OperationRun diagnostics exposure, and test/browser-lane cost.
|
||||
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 11/12**
|
||||
- **Decision**: approve.
|
||||
|
||||
### Red Flag Defense
|
||||
|
||||
This spec is deliberately constrained to four confirmed Spec 407 findings. It improves readiness without broadening architecture, adding pages, adding product states, or replacing completed report/operations/provider foundations. The higher test and browser proof cost is justified because the source findings are browser-readiness and customer-safe trust issues.
|
||||
|
||||
## Spec Scope Fields *(mandatory)*
|
||||
|
||||
- **Scope**: canonical-view and workspace/environment-scoped admin surfaces.
|
||||
- **Primary Routes**:
|
||||
- Review pack detail and actions through `ReviewPackResource` and `ViewReviewPack`.
|
||||
- Signed review pack download/report routes: `/admin/review-packs/{reviewPack}/download`, `/admin/review-packs/{reviewPack}/report`, `/admin/management-report-pdfs/{storedReport}/download`.
|
||||
- Operations index/detail routes: `/admin/workspaces/{workspace}/operations`, `/admin/workspaces/{workspace}/operations/{run}`.
|
||||
- Finding detail route through `FindingResource`.
|
||||
- Provider connections list/detail/create no-access behavior through `/admin/provider-connections...`.
|
||||
- **Data Ownership**: existing workspace-owned and managed-environment-owned records only: `ReviewPack`, `StoredReport`, `EnvironmentReview`, `OperationRun`, `Finding`, and `ProviderConnection`. No new tables or ownership model changes.
|
||||
- **RBAC**: preserve workspace membership, managed-environment entitlement, capability-first checks, signed URL authorization, non-member deny-as-not-found where appropriate, and member-but-missing-capability 403 semantics.
|
||||
- **Default filter behavior when tenant-context is active**: operations and provider/finding/report surfaces must preserve existing explicit workspace/environment filters and must not introduce hidden global context.
|
||||
- **Explicit entitlement checks preventing cross-tenant leakage**: every direct record or download access must resolve through existing workspace/environment ownership and policy/service checks before output is shown or served.
|
||||
|
||||
## No Legacy / No Backward Compatibility Constraint *(mandatory)*
|
||||
|
||||
TenantPilot is pre-production unless this spec explicitly records a compatibility exception.
|
||||
|
||||
- **Compatibility posture**: canonical correction of existing behavior.
|
||||
- **Legacy aliases, fallback readers, hidden routes, duplicate UI, old labels, or historical fixtures kept?**: no.
|
||||
- **Why clean replacement is safe now**: the remediation only changes existing pre-production UI/state behavior and tests; no production data or public API compatibility is promised.
|
||||
|
||||
## 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
|
||||
|
||||
## UI/Productization Coverage
|
||||
|
||||
- **Route/page/surface**: review pack detail management PDF action area; management PDF signed download route; operations index/detail; finding detail; provider-connection no-access outcome.
|
||||
- **Current or new page archetype**: existing surfaces only. Review pack is Report/Receipt; operations index/detail is Technical Annex/Receipt for immutable run truth; finding detail is Decision/Secondary Context; provider-connection no-access is Settings/authorization outcome.
|
||||
- **Design depth**: Domain Pattern Surface for report/finding/provider pages; Internal/Hidden or Technical Annex for operations detail.
|
||||
- **Repo-truth level**: repo-verified.
|
||||
- **Existing pattern reused**: Filament resources/pages/actions, `ManagementReportPdfService`, `OperationRunLinks`, `OperationUxPresenter`, `BadgeRenderer`, `UiEnforcement` / `WorkspaceUiEnforcement`, existing policies/middleware, existing signed route controllers.
|
||||
- **New pattern required**: none.
|
||||
- **Screenshot required**: yes, focused browser proof screenshots or browser records for the four affected findings.
|
||||
- **Page audit required**: no new full audit; focused page proof only.
|
||||
- **Customer-safe review required**: yes for review/report and finding hash demotion.
|
||||
- **Dangerous-action review required**: yes only to confirm no destructive/high-impact action is introduced or weakened; management PDF generation remains existing high-impact/queued behavior with confirmation and authorization.
|
||||
- **Coverage files updated or explicitly not needed**:
|
||||
- [ ] `docs/ui-ux-enterprise-audit/route-inventory.md`
|
||||
- [ ] `docs/ui-ux-enterprise-audit/design-coverage-matrix.md`
|
||||
- [ ] `docs/ui-ux-enterprise-audit/page-reports/...`
|
||||
- [ ] `docs/ui-ux-enterprise-audit/strategic-surfaces.md`
|
||||
- [ ] `docs/ui-ux-enterprise-audit/grouped-follow-up-candidates.md`
|
||||
- [ ] `docs/ui-ux-enterprise-audit/unresolved-pages.md`
|
||||
- [x] `N/A - no new reachable UI surface; existing surface behavior is remediated and proof is captured in this spec package's implementation report`
|
||||
- **No-impact rationale when applicable**: N/A.
|
||||
|
||||
## Product Surface Impact *(mandatory for UI-affecting specs)*
|
||||
|
||||
Reference: `docs/product/standards/product-surface-contract.md`.
|
||||
|
||||
- **Product Surface Contract applies?**: yes; rendered UI/report/download/authorization behavior changes are in scope.
|
||||
- **Page archetype**:
|
||||
- Review pack detail / report action area: Report Page / Receipt Page.
|
||||
- Operations index/detail: Technical Annex Page / Receipt Page.
|
||||
- Finding detail: Decision Page / Secondary Context.
|
||||
- Provider-connection no-access: Settings Page denial outcome.
|
||||
- **Primary user question**:
|
||||
- Review pack: Is the management-ready output ready to open/download, or does it need generation?
|
||||
- Operations: What happened, can I trust the run state, and can I navigate without runtime failure?
|
||||
- Finding detail: What is the human-readable risk and next action?
|
||||
- Provider no-access: Am I blocked because I lack access, without implying I am unauthenticated?
|
||||
- **Primary action**:
|
||||
- Ready PDF exists: Download/Open management PDF.
|
||||
- No ready PDF exists: Generate management PDF only when allowed by existing readiness and authorization rules.
|
||||
- Operations detail: Refresh or follow the existing primary next action, without blocking page readiness.
|
||||
- Finding detail: act on the finding workflow action already authorized for the user.
|
||||
- Provider no-access: return to safe authorized context or accept the no-access result.
|
||||
- **Surface budget result**: pass expected. Operations technical pages may use Technical Annex depth; no product-facing page may show raw identifiers by default.
|
||||
- **Technical Annex / deep-link demotion**: raw fingerprints, scope hashes, internal OperationRun proof links, raw evidence IDs, source keys, payloads, provider payloads, and diagnostic logs must be hidden, collapsed, or moved to support/operator-only detail when not needed for the first decision.
|
||||
- **Canonical status vocabulary**: use `Ready`, `Needs attention`, `Blocked`, `Running`, `Failed`, `Expired`, `Not configured`, `Unknown`, `Historical`, `Superseded`, and allowed severity values only for top-level product states.
|
||||
- **Visible complexity impact**: decreased or neutral. The pack must remove contradictory report/PDF cues and demote technical noise.
|
||||
- **Product Surface exceptions**: none approved.
|
||||
|
||||
## Browser Verification Plan *(mandatory)*
|
||||
|
||||
- **Browser proof required?**: yes.
|
||||
- **No-browser rationale**: N/A.
|
||||
- **Focused path when required**:
|
||||
1. Review pack with ready stored management PDF shows ready/download state.
|
||||
2. Review pack without ready PDF or with failed/missing/unavailable PDF shows generate/unavailable state correctly.
|
||||
3. Authorized management PDF download/open works.
|
||||
4. Unauthorized or unsigned report/PDF path remains blocked.
|
||||
5. Operations index completes browser navigation without timeout.
|
||||
6. Operations detail completes browser navigation without timeout.
|
||||
7. Finding detail default view no longer prominently exposes raw hashes.
|
||||
8. Readonly provider-connection route produces safe/clear no-access behavior.
|
||||
- **Primary interaction to execute**: navigation, table/list open, action state inspection, signed download/open, direct unauthorized access, default detail inspection.
|
||||
- **Console, Livewire, Filament, network, and 500-error checks**: planned.
|
||||
- **Full-suite failure triage**: unrelated full-browser failures may be documented only if focused affected proof is green and no touched route fails.
|
||||
|
||||
## Human Product Sanity Check *(mandatory)*
|
||||
|
||||
- **Required?**: yes.
|
||||
- **No-human-sanity rationale**: N/A.
|
||||
- **Reviewer questions**: purpose clear, one dominant next action, technical details demoted, status labels canonical, complexity not worse, customer/operator trust acceptable.
|
||||
- **Planned result location**: `specs/412-pilot-readiness-remediation-pack/implementation-report.md` during implementation close-out.
|
||||
|
||||
## Product Surface Merge Gate Checklist *(mandatory)*
|
||||
|
||||
- [x] No-legacy posture or approved exception recorded.
|
||||
- [x] Product Surface Impact is completed.
|
||||
- [x] Browser proof is required.
|
||||
- [x] Human Product Sanity is required.
|
||||
- [x] Product Surface exceptions are documented as `none`.
|
||||
- [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)**: report/download action links, status messaging, technical detail disclosure, authorization/no-access messaging, OperationRun navigation, browser proof reporting.
|
||||
- **Systems touched**: `ReviewPackResource`, `ViewReviewPack`, `ManagementReportPdfService`, signed report controllers, operations pages, `OperationRunLinks`, `FindingResource`, `ProviderConnectionResource`, policies/middleware, localization/copy where relevant, and existing test helpers.
|
||||
- **Existing pattern(s) to extend**: native Filament actions/infolists, existing BadgeRenderer domains, OperationRun URL helpers, ManagementReport PDF service decisions, existing authorization helpers.
|
||||
- **Shared contract / presenter / builder / renderer to reuse**: `OperationUxPresenter`, `OperationRunLinks`, `BadgeRenderer`, `UiEnforcement` / `WorkspaceUiEnforcement`, existing report/PDF services and signed URL generation.
|
||||
- **Why the existing shared path is sufficient or insufficient**: current repo surfaces already contain the needed report, operations, finding, and provider foundations; the defects are local behavior and proof gaps, not missing architecture.
|
||||
- **Allowed deviation and why**: none approved. Any implementation-time deviation must be documented as a bounded Product Surface exception before runtime UI edits continue.
|
||||
- **Consistency impact**: ready/download/generate copy, signed download behavior, report state truth, operations route readiness, technical annex disclosure, and no-access semantics must stay consistent across tests, browser proof, and implementation report.
|
||||
- **Review focus**: verify no new local product state, new navigation, duplicate report state, ad-hoc operation link, or raw diagnostic default content is introduced.
|
||||
|
||||
## OperationRun UX Impact
|
||||
|
||||
- **Touches OperationRun start/completion/link UX?**: yes, but only existing management PDF generation and operations navigation/link readiness are in scope.
|
||||
- **Shared OperationRun UX contract/layer reused**: existing `OperationUxPresenter`, `OperationRunLinks`, OperationRun lifecycle services, and operations pages.
|
||||
- **Delegated start/completion UX behaviors**: existing queued toast, run link, artifact link, run-enqueued browser event, dedupe/block messaging, and tenant/workspace-safe URL resolution must remain delegated to shared paths.
|
||||
- **Local surface-owned behavior that remains**: initiation inputs and local action visibility only.
|
||||
- **Queued DB-notification policy**: no new opt-in; existing policy remains.
|
||||
- **Terminal notification path**: central lifecycle mechanism remains.
|
||||
- **Exception required?**: none.
|
||||
|
||||
## Provider Boundary / Platform Core Check
|
||||
|
||||
- **Shared provider/platform boundary touched?**: yes, bounded provider-connection no-access behavior and finding/provider diagnostic disclosure only.
|
||||
- **Boundary classification**: mixed. Provider connection remains provider-owned at integration detail, while no-access and workspace/environment authorization are platform-core.
|
||||
- **Seams affected**: provider-connection routes/pages/guards, managed-environment membership/capability checks, provider diagnostic labels if rendered.
|
||||
- **Neutral platform terms preserved or introduced**: workspace, managed environment, provider connection, no access, permission, membership, readiness.
|
||||
- **Provider-specific semantics retained and why**: Microsoft provider-specific details remain only where the existing provider connection requires them.
|
||||
- **Why this does not deepen provider coupling accidentally**: the spec does not add provider types, provider registries, target-scope taxonomies, or new provider runtime behavior.
|
||||
- **Follow-up path**: none for this pack; broader provider readiness/onboarding productization remains separate.
|
||||
|
||||
## 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 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Review pack detail management PDF state/action | yes | Native Filament + existing report services | reports, action links, signed downloads | detail, action state | no | existing surface only |
|
||||
| Operations index/detail route readiness | yes | Native Filament Page + existing OperationRun helpers | monitoring, OperationRun links | page, detail, route load | no | existing surface only |
|
||||
| Finding detail hash demotion | yes | Native Filament infolist | technical evidence disclosure | detail | no | existing surface only |
|
||||
| Provider-connection no-access outcome | yes | Filament resource/middleware/policy behavior | authorization messaging | route, page/redirect outcome | no | existing surface 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 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| Review pack detail PDF action area | Secondary Context Surface | Decide whether to open/download ready management output or generate it | ready/missing/failed/blocked PDF state and one dominant action | operation/run proof and artifact internals | secondary because review workflow owns broader decision | report consumption | removes contradictory generate/download search |
|
||||
| Operations index/detail | Tertiary Evidence / Diagnostics Surface | Inspect operational proof when troubleshooting | run identity, status, outcome, human reason | raw context, support diagnostics, related internals | not primary; evidence/proof support | monitoring/audit | page readiness prevents audit blockers |
|
||||
| Finding detail | Secondary Context Surface | Triage finding and next action | status, severity, human scope, recommendation | fingerprint/scope hashes and raw support detail | secondary decision detail | finding governance | demotes support identifiers |
|
||||
| Provider-connection no-access | Secondary Context Surface | Understand blocked access safely | clear no-access reason or safe denied outcome | none | not primary; prevents confusion | provider setup/readiness | avoids misleading login/no-membership prompt |
|
||||
|
||||
## 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 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| Review pack detail | operator-MSP, customer-safe output reader where authorized | report readiness, download/generate availability, customer-safe report context | report lifecycle and active run if needed | raw file path, stack traces, internal payloads | Download/Open or Generate based on state | raw report artifact internals | one report/PDF truth wins |
|
||||
| Operations pages | operator-MSP, support-platform | operation status/outcome and trusted navigation | filters, related links, support diagnostics | raw context/failures/logs | Refresh or existing primary run action | raw payload/log details | one run state summary |
|
||||
| Finding detail | operator-MSP, customer/read-only where applicable, support-platform | title, severity, human scope, status, next action | evidence/proof links | fingerprint, scope hash, source fingerprint | existing finding workflow action | hashes/raw support detail | no duplicate technical summary |
|
||||
| Provider no-access | readonly, unauthorized, operator-MSP | blocked access meaning | permission/membership explanation where safe | record existence, provider payload | safe return/no-access acknowledgement | record existence for non-members | no login-vs-auth ambiguity |
|
||||
|
||||
## 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 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| Review pack detail | Record / Detail / Edit | Report / Receipt detail | Download ready management PDF or generate when allowed | detail header action | N/A | header secondary/More | none introduced | existing review pack list | existing review pack view | workspace/environment | Review Pack | pack readiness and PDF state | none |
|
||||
| Operations index | Monitoring / Queue / Workbench | History / Audit Surface | Open or filter run | row/open link | existing | related navigation | none | `/admin/workspaces/{workspace}/operations` | `/admin/workspaces/{workspace}/operations/{run}` | workspace/environment filter | Operation | status/outcome/load result | technical surface depth allowed |
|
||||
| Finding detail | Record / Detail / Edit | Decision detail | resolve/assign/exception existing action | detail view | N/A | More/header | existing confirmation rules | existing findings list | existing finding view | workspace/environment | Finding | status/severity/human scope | none |
|
||||
| Provider no-access | Settings Page | Authorization outcome | understand denied access | route outcome | N/A | N/A | none | `/admin/provider-connections` | `/admin/provider-connections/{record}` | workspace/environment | Provider Connection | no-access reason | none |
|
||||
|
||||
## UI Action Matrix
|
||||
|
||||
| Surface | Header Actions | Row Actions | Bulk Actions | Empty-State CTA | Primary Action | Secondary Actions | Destructive Actions | Technical / Audit Actions |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| Review pack detail PDF action area | Existing detail header action area owns management PDF state; ready PDF makes Download/Open primary, generation stays available only when allowed | N/A | N/A | N/A | Download/Open management PDF when ready; Generate management PDF only when no ready PDF exists and generation is allowed | Existing report preview/download/regenerate actions stay secondary or grouped according to current `ReviewPackResource` contract | None new; existing expire/regenerate/generation safeguards stay confirmed and authorized | OperationRun proof and artifact internals stay secondary/internal |
|
||||
| Operations index | Existing list header keeps scope/filter/return context only | Existing row/open affordance opens canonical run detail | None | Existing operations empty state remains explanatory and non-mutating | Open/filter operation run context without blocking page readiness | Related navigation and filters remain secondary | None | Raw context, failures, support diagnostics, and proof details stay technical/secondary |
|
||||
| Finding detail | Existing detail header owns authorized finding workflow actions | N/A | N/A | N/A | Existing authorized finding workflow action remains primary | Related evidence/proof links and diagnostics stay secondary | Existing destructive/governance-changing actions keep confirmation and authorization | Fingerprint, scope hash, source fingerprint, detector/source keys, and raw support detail move out of default body |
|
||||
| Provider-connection no-access | N/A for denied outcome; authorized provider resource header remains unchanged | N/A for denied outcome | N/A | N/A | Safe no-access result or safe return path for authenticated unauthorized actor | Permission/membership explanation where safe | None | Record existence, provider payload, and internal provider diagnostics remain hidden for non-members/cross-workspace actors |
|
||||
|
||||
## 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 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| Review pack detail PDF action area | Workspace admin / operator | consume or generate management output | Report / Receipt | Is a valid management PDF ready? | ready/missing/failed/blocked state and action | active run, file internals | report lifecycle, file availability, authorization | TenantPilot/report generation only | Download/Open or Generate | none new |
|
||||
| Operations index/detail | Workspace admin / support operator | inspect run proof | Technical Annex / Receipt | Did this operation complete and can I trust it? | run status/outcome/reason | raw context, support diagnostics | execution outcome, lifecycle | none for rendering | Refresh/open related | existing only |
|
||||
| Finding detail | Tenant operator | triage finding | Decision detail | What should I do about this finding? | title, severity, human scope, status, next action | hashes, raw source fingerprints | finding status, severity, responsibility | existing finding workflow | existing workflow action | existing only |
|
||||
| Provider no-access | readonly / limited actor | understand blocked provider access | Settings denial | Why can I not open this provider surface? | safe no-access message | none | capability/membership outcome | none | safe return / no access | none |
|
||||
|
||||
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||
|
||||
- **New source of truth?**: no.
|
||||
- **New persisted entity/table/artifact?**: no.
|
||||
- **New abstraction?**: no approved.
|
||||
- **New enum/state/reason family?**: no.
|
||||
- **New cross-domain UI framework/taxonomy?**: no.
|
||||
- **Current operator problem**: pilot-readiness blockers on existing surfaces create false or confusing product signals.
|
||||
- **Existing structure is insufficient because**: current local behavior shows contradictory PDF state, browser-timeout risk, technical identifiers in default detail, and unclear no-access outcome.
|
||||
- **Narrowest correct implementation**: targeted fixes in existing resources/pages/controllers/services and focused tests/browser proof.
|
||||
- **Ownership cost**: small test and proof cost; no new long-term conceptual system.
|
||||
- **Alternative intentionally rejected**: broad report architecture rewrite, operations dashboard redesign, new finding taxonomy, provider onboarding productization, or another full browser audit.
|
||||
- **Release truth**: current-release pilot hardening.
|
||||
|
||||
### Compatibility posture
|
||||
|
||||
This feature assumes a pre-production environment. Backward compatibility, legacy aliases, migration shims, duplicate UI, and compatibility-specific fixtures are out of scope.
|
||||
|
||||
## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
|
||||
|
||||
- **Test purpose / classification**: Feature, Filament/Livewire, authorization, and focused Browser.
|
||||
- **Validation lane(s)**: fast-feedback, confidence, browser; PostgreSQL lane only if implementation touches database constraints or PostgreSQL-specific query behavior, which is not expected.
|
||||
- **Why this classification and these lanes are sufficient**: the defects are rendered state, route readiness, authorization outcomes, signed downloads, and browser load behavior over existing persistence.
|
||||
- **New or expanded test families**: no broad new family. Add focused tests near existing ReviewPack, Monitoring/Operations, Finding, ProviderConnections, and browser smoke coverage.
|
||||
- **Fixture / helper cost impact**: reuse existing factories and helpers such as `createUserWithTenant`, `spec379CurrentReadyPack`, `spec379ReadyManagementPdf`, operations factories, and provider/finding helpers. Avoid new global seeds.
|
||||
- **Heavy-family visibility / justification**: browser proof is explicit and focused because the source findings were browser-audit findings.
|
||||
- **Special surface test profile**: shared-detail-family for review/finding, monitoring-state-page for operations, exception-coded-surface for provider no-access.
|
||||
- **Standard-native relief or required special coverage**: ordinary Filament/Pest tests for state and authorization; focused browser proof for rendered route completion and visual/default content.
|
||||
- **Reviewer handoff**: verify lane fit, no hidden full-audit claim, no broad fixtures, no new route/page/surface, and no completed-spec rewrite.
|
||||
- **Budget / baseline / trend impact**: none expected; if operations route load fix changes query cost materially, document in implementation report.
|
||||
- **Escalation needed**: document-in-feature for any bounded residual P2/P3; follow-up-spec only if remediation discovers structural report/operations/provider defects beyond this pack.
|
||||
- **Active feature PR close-out entry**: Product Surface / Focused Browser Proof / Pilot Readiness Remediation.
|
||||
- **Planned validation commands**:
|
||||
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=ReviewPack`
|
||||
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=ManagementReport`
|
||||
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=OperationRun`
|
||||
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Operations`
|
||||
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=Finding`
|
||||
- `cd apps/platform && ./vendor/bin/sail artisan test --filter=ProviderConnection`
|
||||
- focused browser smoke for the eight proof paths listed in this spec
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Ready Management PDF Is Surfaced Correctly (Priority: P1)
|
||||
|
||||
A workspace admin opens an existing review pack where a ready management PDF already exists and sees a ready/download/open state instead of a primary "Generate management PDF" action.
|
||||
|
||||
**Why this priority**: Spec 407 classified this as the main P1 blocking customer-facing hardening.
|
||||
|
||||
**Independent Test**: Create a ready review pack with a ready `StoredReport::REPORT_TYPE_MANAGEMENT_REPORT_PDF`, render the review pack detail, and assert the primary state is ready/download while generate is not the primary action.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a ready stored management PDF exists for the current review pack, **When** an authorized admin opens the review pack detail, **Then** the primary action is download/open and not generate.
|
||||
2. **Given** no ready PDF exists, **When** generation is allowed, **Then** the generate action remains available and accurately labeled.
|
||||
3. **Given** the PDF record is failed, missing, expired, or inconsistent with file storage, **When** the page renders, **Then** the page shows failed/unavailable/inconsistent state and does not serve it as ready.
|
||||
4. **Given** an unauthorized or cross-workspace actor requests the management PDF directly, **When** authorization is evaluated, **Then** the download remains blocked and no file data leaks.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Operations Routes Complete Browser Navigation (Priority: P1)
|
||||
|
||||
A workspace admin or support operator opens the operations index and an operation detail route, and both pages complete usable browser navigation without 500s, fatal Livewire/Filament errors, or indefinite readiness blockers.
|
||||
|
||||
**Why this priority**: Spec 407 observed browser navigation timeouts for operations routes even though no current 500 was reproduced.
|
||||
|
||||
**Independent Test**: Render operations index/detail with representative completed/failed/running runs and assert DB-only behavior, scoped visibility, bounded query/load behavior, and focused browser navigation completion.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an entitled workspace admin, **When** they navigate to `/admin/workspaces/{workspace}/operations`, **Then** navigation completes and the page is usable.
|
||||
2. **Given** an entitled actor opens `/admin/workspaces/{workspace}/operations/{run}`, **When** the run belongs to their workspace/entitled environment, **Then** navigation completes and the detail renders.
|
||||
3. **Given** the page has polling or long-running Livewire requests, **When** the browser audit waits for page readiness, **Then** readiness is detectable and does not time out because of indefinite requests.
|
||||
4. **Given** historical OperationRun route errors existed in browser logs, **When** the current focused proof runs, **Then** any current error is fixed or the historical condition is documented as not reproducible.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Finding Detail Demotes Internal Hashes (Priority: P2)
|
||||
|
||||
A tenant operator opens a finding detail page and sees human-readable risk, affected scope, evidence, and next action by default while raw fingerprints and scope/source hashes are hidden, collapsed, or support-only.
|
||||
|
||||
**Why this priority**: raw hashes in default body weaken customer/operator trust and violate the Product Surface Contract's technical-demotion rule.
|
||||
|
||||
**Independent Test**: Render a finding with fingerprint/source hash fields and assert the default body does not prominently show raw hash labels or values while authorized support/operator diagnostics can still access them when needed.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a finding contains fingerprint and scope/source hash values, **When** an ordinary operator opens the default detail view, **Then** those values are not prominent default body content.
|
||||
2. **Given** support diagnostics need hash values, **When** an authorized support/operator opens the technical detail disclosure, **Then** the values remain accessible without leaking into customer/read-only default content.
|
||||
3. **Given** a customer-readable finding/report surface exists, **When** it renders the finding, **Then** raw hashes are absent unless an existing explicit contract permits them.
|
||||
|
||||
---
|
||||
|
||||
### User Story 4 - Readonly Provider No-Access Is Clear And Safe (Priority: P3)
|
||||
|
||||
A readonly or limited actor who is authenticated but not entitled to a provider-connection route receives a clear no-access outcome, while authorization and non-member record-hiding remain unchanged.
|
||||
|
||||
**Why this priority**: it is a polish issue, but confusing login or membership copy weakens pilot confidence.
|
||||
|
||||
**Independent Test**: Exercise readonly, unauthorized, and cross-workspace provider-connection paths and assert the response is clearer than a misleading login redirect without revealing forbidden records.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a readonly actor lacks provider access, **When** they open a provider-connection route, **Then** they remain blocked and see a clear no-access/permission outcome.
|
||||
2. **Given** a non-member or cross-workspace actor requests a provider connection, **When** authorization runs, **Then** the response does not reveal record existence.
|
||||
3. **Given** the actor is authenticated, **When** access is denied for capability or membership reasons, **Then** the result must not imply that the user simply needs to log in again unless they are actually unauthenticated.
|
||||
|
||||
## Functional Requirements *(mandatory)*
|
||||
|
||||
- **FR-412-001**: The implementation MUST resolve or product-decide the Spec 407 P1 management PDF surfacing issue.
|
||||
- **FR-412-002**: A ready stored management PDF associated with the current review pack MUST display ready/download/open state to authorized users.
|
||||
- **FR-412-003**: A ready stored management PDF MUST NOT show "Generate management PDF" as the primary action.
|
||||
- **FR-412-004**: Missing, failed, unavailable, expired, deleted, or inconsistent PDF/file states MUST NOT be shown or served as ready.
|
||||
- **FR-412-005**: Management PDF direct downloads MUST preserve signed route, workspace/environment ownership, and authorization checks.
|
||||
- **FR-412-006**: Signed customer report routes MUST still render only when valid and customer-safe; unsigned or invalid report paths remain blocked.
|
||||
- **FR-412-007**: Operations index and detail routes MUST complete usable browser navigation under normal local audit conditions.
|
||||
- **FR-412-008**: Operations pages MUST not perform Graph/external calls during render and MUST not expose stack traces, raw payloads, or internal errors by default.
|
||||
- **FR-412-009**: If polling or pending requests are intentional on operations pages, page readiness MUST remain detectable for focused browser proof.
|
||||
- **FR-412-010**: Finding detail default-visible content MUST not prominently expose fingerprint, scope hash, source fingerprint, detector keys, source keys, or equivalent raw technical identifiers.
|
||||
- **FR-412-011**: Hashes or identifiers needed for support diagnostics MAY remain available only in collapsed, technical, support, or capability-gated detail.
|
||||
- **FR-412-012**: Readonly provider-connection no-access behavior MUST remain denied and MUST improve copy/outcome clarity for authenticated actors.
|
||||
- **FR-412-012a**: Authenticated actors denied by provider permission or membership checks MUST NOT be sent to a login prompt unless they are actually unauthenticated; the outcome MUST be either a 403/no-access response or a safe redirect with accurate permission or membership copy.
|
||||
- **FR-412-013**: Non-member and cross-workspace provider/report/operation access MUST continue to avoid leaking record existence.
|
||||
- **FR-412-014**: No new top-level navigation, major page, product concept, status family, persisted truth, or provider access model may be introduced.
|
||||
- **FR-412-015**: A final implementation report MUST include the Spec 407 Finding Remediation Matrix and Report/PDF State Matrix from the supplied draft.
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
- **NFR-412-001**: Filament v5 code must remain Livewire v4-compatible and use existing panel provider registration in `apps/platform/bootstrap/providers.php`.
|
||||
- **NFR-412-002**: All rendered UI changes must use native Filament/shared primitives first and avoid custom styling or duplicate UI concepts.
|
||||
- **NFR-412-003**: No Graph call or external HTTP call may be introduced during page render.
|
||||
- **NFR-412-004**: Download/report routes must avoid secret, path, payload, stack trace, or raw provider leakage.
|
||||
- **NFR-412-005**: Tests must prove business truth, authorization, customer-safe boundaries, route readiness, and negative access paths without broad fixture bloat.
|
||||
- **NFR-412-006**: Browser proof must be focused and must not claim a new full browser/UX/runtime audit.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- New report template, PDF renderer, PDF lifecycle architecture, or report generation workflow.
|
||||
- New review-pack product concept, customer review surface, customer portal, operations dashboard, or provider onboarding flow.
|
||||
- New OperationRun architecture, state model, or notification policy.
|
||||
- New finding taxonomy, persisted reason/status family, or cross-domain UI framework.
|
||||
- Legal hold, purge, export-before-delete governance, staging/Dokploy validation, JSONB migration, commercial lifecycle, or support desk integration.
|
||||
- Reopening completed Specs 400-407 or rewriting their close-out, validation, browser, smoke, screenshot, or task history.
|
||||
- Full browser/UX/runtime audit.
|
||||
|
||||
## Key Entities And Truth Sources
|
||||
|
||||
- **ReviewPack**: existing review output package and detail surface context.
|
||||
- **StoredReport**: existing report/PDF artifact truth, including management report PDF state and file metadata.
|
||||
- **EnvironmentReview**: existing released/customer-safe review context.
|
||||
- **OperationRun**: canonical execution truth for operations pages.
|
||||
- **Finding**: existing finding state and technical identifiers.
|
||||
- **ProviderConnection**: existing provider connection readiness/access record.
|
||||
- **AuditLog**: existing audit trail for security-relevant or mutating actions; no new audit domain is introduced.
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- StoredReport is `ready` but file is missing, size/hash invalid, storage disk unavailable, or signed URL cannot be generated.
|
||||
- Review pack is expired/archived/deleted but a PDF record exists.
|
||||
- A PDF generation run is already queued/running while a ready PDF also exists.
|
||||
- Operations page intentionally keeps a polling request open.
|
||||
- OperationRun belongs to the workspace but references an environment the actor cannot access.
|
||||
- Finding has both human-readable scope and hash fields; only the former should be default-visible.
|
||||
- Readonly actor is authenticated but lacks provider view/manage capability.
|
||||
- Non-member actor probes provider/report/operation direct URLs.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
- **SC-412-001**: Controlled pilot can proceed without the Spec 407 management PDF/action clarity exclusion.
|
||||
- **SC-412-002**: No P0 or P1 residual finding remains in the four included areas.
|
||||
- **SC-412-003**: Any remaining P2/P3 is explicitly bounded, documented, and non-blocking for scripted pilot paths.
|
||||
- **SC-412-004**: Focused tests and browser proof pass for all four included findings.
|
||||
- **SC-412-005**: Customer-safe/report/authorization boundaries remain intact.
|
||||
- **SC-412-006**: No new product surface, product concept, persisted truth, status family, or broad abstraction is introduced.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- Spec 407 findings are accepted as valid source findings even if the final audit report was not committed as a spec-local artifact.
|
||||
- Existing fixtures can be reused or minimally extended to represent ready/missing/failed management PDFs, operations runs, findings with hashes, and readonly provider access.
|
||||
- The management-report PDF staging/Dokploy validation gap remains outside this pack.
|
||||
- The product remains pre-production, so clean correction is preferred over compatibility shims.
|
||||
|
||||
## Risks
|
||||
|
||||
- A ready StoredReport may not be the only source of report truth; implementation must verify `ManagementReportPdfService` and review-pack relationships before changing UI state.
|
||||
- Operation route timeout may be a browser-tool readiness issue rather than a server defect; proof must distinguish this from real runtime failure.
|
||||
- Hiding hashes too aggressively could hurt support workflows; demotion must preserve authorized technical access where needed.
|
||||
- Provider no-access copy could accidentally reveal record existence; non-member and cross-workspace cases must remain safe.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- None blocking preparation. Implementation must record exact reproduction/proof data for each Spec 407 finding before and after remediation.
|
||||
|
||||
## Follow-up Spec Candidates
|
||||
|
||||
- Management-report PDF staging/Dokploy runtime validation remains separate.
|
||||
- Governance artifact lifecycle/retention runtime remains separate.
|
||||
- Provider readiness/onboarding productization remains separate.
|
||||
- Future full browser/UX/runtime re-audit remains separate and only after focused remediation is complete.
|
||||
|
||||
## Candidate Selection Gate Result
|
||||
|
||||
**PASS WITH NUMBERING DEVIATION**. The candidate was directly supplied by the user, aligns with the Spec 407 remediation path, is small enough for a bounded implementation loop, is not already covered by an active or completed spec in the current branch, and excludes completed-spec rewrites. The supplied number 408 could not be used safely because 408 through 411 are already reserved by existing branches.
|
||||
|
||||
## Spec Readiness Gate Result
|
||||
|
||||
**PASS FOR IMPLEMENTATION PREPARATION** once `plan.md`, `tasks.md`, and `checklists/requirements.md` in this package are reviewed together. This spec has no open product question that blocks a later implementation loop.
|
||||
165
specs/412-pilot-readiness-remediation-pack/tasks.md
Normal file
165
specs/412-pilot-readiness-remediation-pack/tasks.md
Normal file
@ -0,0 +1,165 @@
|
||||
# Tasks: Spec 412 - Pilot Readiness Remediation Pack
|
||||
|
||||
**Input**: `specs/412-pilot-readiness-remediation-pack/spec.md`, `plan.md`, `checklists/requirements.md`, user-provided Spec 408 draft, Spec 407 findings, Product Surface Contract, roadmap/spec-candidate truth, and repo inventory.
|
||||
|
||||
**Prerequisites**: Review `AGENTS.md`, `.specify/memory/constitution.md`, `docs/ai-coding-rules.md`, `docs/product/standards/product-surface-contract.md`, `docs/filament-guidelines.md`, `docs/security-guidelines.md`, `docs/testing-guidelines.md`, and this spec package before runtime edits.
|
||||
|
||||
**Tests**: Required. Runtime behavior changes need focused Pest feature/Filament/Livewire tests plus focused browser proof. No full browser audit claim.
|
||||
|
||||
## Test Governance Checklist
|
||||
|
||||
- [x] Lane assignment is named and is the narrowest sufficient proof for each changed behavior.
|
||||
- [x] New or changed tests stay in the smallest honest family, and browser proof is explicit and focused.
|
||||
- [x] Shared helpers, factories, seeds, fixtures, and context defaults stay cheap by default.
|
||||
- [x] Planned validation commands cover the four included findings without pulling in unrelated lane cost.
|
||||
- [x] The declared surface test profiles are explicit: shared-detail-family, monitoring-state-page, exception-coded-surface.
|
||||
- [x] Browser proof is completed for rendered UI changes.
|
||||
- [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: Safety, Inventory, And Reproduction
|
||||
|
||||
**Purpose**: Confirm repo state, locate exact runtime ownership, and reproduce/validate each Spec 407 finding before fixing.
|
||||
|
||||
- [x] T001 Record `git status --short --branch`, `git diff --name-only`, and `git diff --check` before implementation.
|
||||
- [x] T002 Read `specs/412-pilot-readiness-remediation-pack/spec.md`, `plan.md`, `tasks.md`, and `checklists/requirements.md`.
|
||||
- [x] T003 Confirm no completed Specs 400-407 are edited or normalized.
|
||||
- [x] T004 Inventory review/report/PDF routes, resources, controllers, services, and tests related to ReviewPack, StoredReport, ManagementReportPdf, signed downloads, and customer reports.
|
||||
- [x] T005 Inventory operations index/detail routes, `Operations` page, `TenantlessOperationRunViewer`, views, OperationRun link helpers, polling/readiness behavior, and existing tests.
|
||||
- [x] T006 Inventory finding detail rendering, fingerprint/source hash fields, technical detail sections, customer-safe report/finding surfaces, and existing finding tests.
|
||||
- [x] T007 Inventory provider-connection route, resource, middleware/policy, readonly/no-access behavior, and existing ProviderConnections tests.
|
||||
- [x] T008 Reproduce or validate the ready-management-PDF-not-surfaced finding with existing or minimally created local fixtures.
|
||||
- [x] T009 Reproduce or validate operations route browser timeout/no-current-500 behavior with focused browser navigation.
|
||||
- [x] T010 Reproduce or validate finding fingerprint/scope hash default-body exposure.
|
||||
- [x] T011 Reproduce or validate readonly provider-connection no-access copy/redirect behavior.
|
||||
- [x] T012 Document any non-reproducible finding and the proof that makes it non-reproducible without marking it fixed prematurely.
|
||||
|
||||
## Phase 2: Review Pack / Management PDF Surfacing (P1)
|
||||
|
||||
**Goal**: Ready stored management PDFs surface as ready/downloadable, not primarily as generate.
|
||||
|
||||
**Independent Test**: A ready ReviewPack with a ready management-report StoredReport renders a ready/download action for authorized users and keeps unauthorized/cross-workspace direct download blocked.
|
||||
|
||||
### Tests First
|
||||
|
||||
- [x] T013 Add or update a ReviewPack/Filament test proving a ready management PDF renders ready/download/open state on review pack detail.
|
||||
- [x] T014 Add or update a test proving `Generate management PDF` is not the primary action when a valid ready management PDF exists.
|
||||
- [x] T015 Add or update tests for missing, failed, unavailable, expired, or inconsistent PDF/file states so they are not shown or served as ready.
|
||||
- [x] T016 Add or update signed download authorization tests proving authorized download works and unauthorized/cross-workspace direct download remains blocked.
|
||||
- [x] T017 Add or update signed vs unsigned report route tests proving customer report behavior does not regress.
|
||||
|
||||
### Implementation
|
||||
|
||||
- [x] T018 Verify and harden `ManagementReportPdfService::findReadyReport()` and related decision methods so they use the correct same-scope ReviewPack/StoredReport source truth.
|
||||
- [x] T019 Update `apps/platform/app/Filament/Resources/ReviewPackResource/Pages/ViewReviewPack.php` only as needed so ready PDF state wins over generate as the primary state. Existing implementation already satisfied this and was verified by Spec379 tests/browser proof.
|
||||
- [x] T020 Update `ManagementReportPdfService` only if tests prove the UI is reading an incomplete or inconsistent state source. Repeat final review proved incomplete service-level scope checks, so ready/active/retry/run-bound management PDF lookups were hardened.
|
||||
- [x] T021 Preserve existing management PDF generation confirmation, authorization, audit/OperationRun behavior, `OperationUxPresenter` use, and signed download behavior.
|
||||
- [x] T022 Ensure failed/missing/inconsistent PDF states use safe product copy and canonical status vocabulary without adding a new status family.
|
||||
- [x] T023 Confirm customer-safe report boundaries remain intact: no raw OperationRun internals, raw provider payloads, file paths, or stack traces.
|
||||
|
||||
## Phase 3: OperationRun Route Load Completion (P2)
|
||||
|
||||
**Goal**: Operations index/detail complete browser navigation without current 500s, fatal Livewire/Filament errors, or indefinite readiness blockers.
|
||||
|
||||
**Independent Test**: Operations index and detail render DB-only for an entitled workspace actor, hide unauthorized/cross-workspace runs, and complete focused browser navigation.
|
||||
|
||||
### Tests First
|
||||
|
||||
- [x] T024 Add or update operations HTTP/Filament tests proving `/admin/workspaces/{workspace}/operations` renders for an entitled actor.
|
||||
- [x] T025 Add or update operations detail tests proving `/admin/workspaces/{workspace}/operations/{run}` renders for an entitled actor.
|
||||
- [x] T026 Add or update DB-only/no-outbound-HTTP assertions for operations index/detail render paths.
|
||||
- [x] T027 Add or update authorization/isolation tests for workspace, managed environment, tenantless, and cross-workspace OperationRun access.
|
||||
- [x] T028 Add or update tests for bounded query/loading behavior if reproduction points to heavy payloads or unbounded relationships.
|
||||
|
||||
### Implementation
|
||||
|
||||
- [x] T029 Inspect operations page polling, Livewire hydration, table filters/search/pagination, eager loading, and view payloads for browser-readiness blockers.
|
||||
- [x] T030 Fix only the smallest operations page/view/query/readiness issue proven by reproduction. No operations page fix was required; focused proof passed.
|
||||
- [x] T031 Ensure operations pages keep raw payloads, stack traces, debug metadata, and technical internals out of default content.
|
||||
- [x] T032 Ensure any intentional polling or pending request does not prevent browser readiness detection in focused proof.
|
||||
- [x] T033 Preserve canonical `OperationRunLinks` and tenant/workspace-safe URL resolution.
|
||||
- [x] T034 Preserve OperationRun lifecycle truth and avoid direct status/outcome transitions outside `OperationRunService`.
|
||||
|
||||
## Phase 4: Finding Detail Internal Hash Demotion (P2)
|
||||
|
||||
**Goal**: Finding detail default body presents human-readable triage context and demotes raw hashes to technical/support detail if retained.
|
||||
|
||||
**Independent Test**: A finding with fingerprint/source hash values renders default detail without prominent raw hash labels/values while authorized technical detail can still expose needed diagnostics.
|
||||
|
||||
### Tests First
|
||||
|
||||
- [x] T035 Add or update a finding detail render test proving `Fingerprint`, `scope hash`, `source_fingerprint`, and equivalent hash values are not prominent default body content.
|
||||
- [x] T036 Add or update customer/read-only/default-output tests proving raw hashes do not leak into customer-safe/default finding content.
|
||||
- [x] T037 Add or update a support/operator technical detail test only if the implementation retains hashes behind a collapsed or gated section.
|
||||
|
||||
### Implementation
|
||||
|
||||
- [x] T038 Update `apps/platform/app/Filament/Resources/FindingResource.php` only as needed to move `fingerprint` and related hash fields out of the default detail body.
|
||||
- [x] T039 Preserve human-readable finding title, severity, affected scope, evidence/proof link where authorized, recommendation, owner/status, and next action.
|
||||
- [x] T040 If hashes remain accessible, place them in collapsed/support/operator technical detail and gate or demote them according to existing patterns.
|
||||
- [x] T041 Do not remove support diagnostics entirely if an existing workflow depends on them.
|
||||
- [x] T042 Do not create a new finding taxonomy, status family, or diagnostic framework.
|
||||
|
||||
## Phase 5: Readonly Provider-Connection No-Access Clarity (P3)
|
||||
|
||||
**Goal**: Authenticated readonly/limited actors remain blocked from unauthorized provider-connection routes but receive a clearer no-access outcome.
|
||||
|
||||
**Independent Test**: Readonly access remains denied, non-member/cross-workspace access remains non-leaky, and the result no longer misleadingly implies unauthenticated login when the actor is authenticated.
|
||||
|
||||
### Tests First
|
||||
|
||||
- [x] T043 Add or update ProviderConnections tests for readonly provider-connection route no-access behavior.
|
||||
- [x] T044 Add or update tests proving non-member/cross-workspace direct provider-connection access does not leak record existence.
|
||||
- [x] T045 Add or update tests proving member-but-missing-capability receives a 403 or safe denied outcome according to existing policy semantics.
|
||||
- [x] T046 Add or update tests proving no redirect loop and no provider detail leak.
|
||||
- [x] T047 Add or update tests proving an authenticated unauthorized provider actor is not redirected to a login prompt unless actually unauthenticated.
|
||||
|
||||
### Implementation
|
||||
|
||||
- [x] T048 Identify whether the confusing outcome is owned by provider resource authorization, workspace/environment middleware, panel authentication, or copy/flash handling.
|
||||
- [x] T049 Improve only the owning route/resource/middleware/copy path needed for authenticated readonly clarity.
|
||||
- [x] T050 Preserve provider view/manage capability checks and workspace/environment membership rules.
|
||||
- [x] T051 Preserve deny-as-not-found semantics for non-members and cross-workspace actors.
|
||||
- [x] T052 Do not expand provider access, provider onboarding, or provider readiness productization.
|
||||
|
||||
## Phase 6: Product Surface, Browser Proof, And Close-Out
|
||||
|
||||
**Goal**: Prove the four remediations without claiming a new full browser audit.
|
||||
|
||||
- [x] T053 Run focused browser proof for a ready management PDF review pack detail state.
|
||||
- [x] T054 Run focused browser proof for missing/failed/unavailable PDF state where fixture support exists.
|
||||
- [x] T055 Run focused browser proof for authorized management PDF download/open. Browser proof verifies the rendered download action; feature tests verify the signed binary route.
|
||||
- [x] T056 Run focused browser proof for unauthorized or unsigned report/PDF path blocked. Server-side signed/unsigned and cross-workspace route blocking is covered by focused feature tests; browser proof covers rendered action state.
|
||||
- [x] T057 Run focused browser proof for operations index navigation completion.
|
||||
- [x] T058 Run focused browser proof for operations detail navigation completion.
|
||||
- [x] T059 Run focused browser proof for finding detail default view without prominent raw hashes.
|
||||
- [x] T060 Run focused browser proof for readonly provider-connection no-access behavior. Browser proof covers the rendered provider no-access outcome; feature tests cover the member-missing-capability redirect branch.
|
||||
- [x] T061 Capture browser console, Livewire/Filament errors, network failures, 500s, and redirect-loop evidence for every focused proof path.
|
||||
- [x] T062 Complete Human Product Sanity for affected review/report, operations, finding, and provider no-access surfaces.
|
||||
- [x] T063 Create `specs/412-pilot-readiness-remediation-pack/implementation-report.md` with the exact report sections required by the source draft.
|
||||
- [x] T064 Complete the Spec 407 Finding Remediation Matrix in the implementation report.
|
||||
- [x] T065 Complete the Report/PDF State Matrix in the implementation report.
|
||||
- [x] T066 Record Product Surface exceptions as `none` or document a bounded exception with follow-up before merge.
|
||||
- [x] T067 Record UI Action Matrix confirmation, 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.
|
||||
|
||||
## Phase 7: Validation
|
||||
|
||||
- [x] T068 Run `cd apps/platform && ./vendor/bin/sail artisan test --filter=ReviewPack`. Ran; broad lane has unrelated residual failures recorded in the implementation report while in-scope ReviewPack/PDF tests passed.
|
||||
- [x] T069 Run `cd apps/platform && ./vendor/bin/sail artisan test --filter=ManagementReport`. Ran; Spec379/404 management PDF tests passed, older Spec366 rendered-report browser test failed and is recorded in the implementation report.
|
||||
- [x] T070 Run `cd apps/platform && ./vendor/bin/sail artisan test --filter=OperationRun`. Ran; an in-scope `report.management.generate` actionability registry gap was fixed, final manual review added ready management PDF artifact-resolution proof, repeat final review hardened the underlying management PDF service lookups, and focused operation tests passed; remaining broad residuals are recorded in the implementation report.
|
||||
- [x] T071 Run `cd apps/platform && ./vendor/bin/sail artisan test --filter=Operations`. Ran; focused operations route/readiness tests passed and broad residual failures are recorded in the implementation report.
|
||||
- [x] T072 Run `cd apps/platform && ./vendor/bin/sail artisan test --filter=Finding`. Ran; focused finding demotion tests passed and broad residual failures are recorded in the implementation report.
|
||||
- [x] T073 Run `cd apps/platform && ./vendor/bin/sail artisan test --filter=ProviderConnection`. Ran; focused provider no-access tests passed and broad residual failures are recorded in the implementation report.
|
||||
- [x] T074 Run the smallest broader relevant suite after targeted tests pass, normally `cd apps/platform && ./vendor/bin/sail artisan test`. Full suite not run because the broader validation filters already expose unrelated residual failures; final expanded focused suite passed with 131 tests and 871 assertions.
|
||||
- [x] T075 Run `git diff --check`.
|
||||
- [x] T076 Record final `git status --short --branch` in the implementation report.
|
||||
|
||||
## Non-Goals / Stop Conditions
|
||||
|
||||
- [x] NT001 Do not create new report templates, PDF renderer architecture, report workflow architecture, review-pack product concepts, or customer review surfaces.
|
||||
- [x] NT002 Do not create a new operations dashboard, OperationRun state model, finding taxonomy, provider onboarding flow, or provider access model.
|
||||
- [x] NT003 Do not implement legal hold, purge, export-before-delete governance, staging/Dokploy validation, JSONB migration, commercial lifecycle, support desk integration, or full browser audit.
|
||||
- [x] NT004 Do not add top-level navigation or major pages.
|
||||
- [x] NT005 Do not introduce new persisted entities, status families, enums, source-of-truth objects, provider registries, or cross-domain UI frameworks.
|
||||
- [x] NT006 Do not rewrite completed Specs 400-407 or remove validation, task, smoke, browser, screenshot, close-out, or review history from completed specs.
|
||||
- [x] NT007 Stop and update spec/plan before continuing if a fix requires broader architecture or product decisions beyond the four included findings.
|
||||
Loading…
Reference in New Issue
Block a user