From 780ed0391ae175150fbbdec4cd71cc61ed337d71 Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Sat, 13 Jun 2026 11:02:28 +0200 Subject: [PATCH] feat(guard): implement ui bloat regression guard Added UiBloatRegressionGuardTest to enforce known UI bloat and customer/auditor safety regression patterns across configured runtime UI source paths as defined in Spec 375. Registered the test in Pest.php and added to TestLaneManifest. --- .../Guards/UiBloatRegressionGuardTest.php | 179 ++++ apps/platform/tests/Pest.php | 1 + .../tests/Support/TestLaneManifest.php | 28 + .../tests/Support/UiBloat/UiBloatScanner.php | 810 ++++++++++++++++++ .../artifacts/affected-files.md | 18 + .../artifacts/allowlist-policy.md | 57 ++ .../artifacts/follow-up-recommendations.md | 23 + .../artifacts/guard-rules.md | 38 + .../artifacts/initial-scan-report.md | 89 ++ .../artifacts/scanner-design.md | 72 ++ .../artifacts/source-summary.md | 60 ++ .../artifacts/validation-report.md | 55 ++ .../checklists/requirements.md | 45 + specs/375-ui-bloat-regression-guard/plan.md | 258 ++++++ specs/375-ui-bloat-regression-guard/spec.md | 359 ++++++++ specs/375-ui-bloat-regression-guard/tasks.md | 136 +++ 16 files changed, 2228 insertions(+) create mode 100644 apps/platform/tests/Feature/Guards/UiBloatRegressionGuardTest.php create mode 100644 apps/platform/tests/Support/UiBloat/UiBloatScanner.php create mode 100644 specs/375-ui-bloat-regression-guard/artifacts/affected-files.md create mode 100644 specs/375-ui-bloat-regression-guard/artifacts/allowlist-policy.md create mode 100644 specs/375-ui-bloat-regression-guard/artifacts/follow-up-recommendations.md create mode 100644 specs/375-ui-bloat-regression-guard/artifacts/guard-rules.md create mode 100644 specs/375-ui-bloat-regression-guard/artifacts/initial-scan-report.md create mode 100644 specs/375-ui-bloat-regression-guard/artifacts/scanner-design.md create mode 100644 specs/375-ui-bloat-regression-guard/artifacts/source-summary.md create mode 100644 specs/375-ui-bloat-regression-guard/artifacts/validation-report.md create mode 100644 specs/375-ui-bloat-regression-guard/checklists/requirements.md create mode 100644 specs/375-ui-bloat-regression-guard/plan.md create mode 100644 specs/375-ui-bloat-regression-guard/spec.md create mode 100644 specs/375-ui-bloat-regression-guard/tasks.md diff --git a/apps/platform/tests/Feature/Guards/UiBloatRegressionGuardTest.php b/apps/platform/tests/Feature/Guards/UiBloatRegressionGuardTest.php new file mode 100644 index 00000000..56d4e620 --- /dev/null +++ b/apps/platform/tests/Feature/Guards/UiBloatRegressionGuardTest.php @@ -0,0 +1,179 @@ + 'UIBLOAT_CUSTOMER_RAW_ID', + 'file' => 'apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php', + 'pattern' => 'operation id', + 'reason' => 'Appears only inside collapsed technical details.', + 'surface_type' => 'customer-auditor', + 'audience' => 'operator-support', + 'review_marker' => 'manual-review-required', + 'expires_or_review_after' => '2026-09-01', + 'owner_spec' => 'specs/375-ui-bloat-regression-guard', + ], $overrides); +} + +it('keeps the ui bloat guard in surface-guard heavy-governance ownership', function (): void { + $family = TestLaneManifest::family('ui-bloat-regression-guard'); + + expect($family['classificationId'])->toBe('surface-guard') + ->and($family['targetLaneId'])->toBe('heavy-governance') + ->and($family['hotspotFiles'])->toContain('tests/Feature/Guards/UiBloatRegressionGuardTest.php'); +}); + +it('supports report warn and fail strictness semantics', function (): void { + $file = 'apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php'; + $source = '
Customer review output

Operation context

'; + + $report = UiBloatScanner::scanSource($file, $source, UiBloatScanner::STRICTNESS_REPORT); + $warn = UiBloatScanner::scanSource($file, $source, UiBloatScanner::STRICTNESS_WARN); + $fail = UiBloatScanner::scanSource( + 'apps/platform/app/Filament/Pages/Operations.php', + '

0 alerts

', + UiBloatScanner::STRICTNESS_FAIL, + ); + + expect($report['blocking_failures'])->toBeEmpty() + ->and($warn['blocking_failures'])->not->toBeEmpty() + ->and($fail['blocking_failures'])->not->toBeEmpty(); +}); + +it('hard-fails customer raw id labels unless allowlisted', function (): void { + $file = 'apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php'; + $source = '
Customer review output

Operation ID

'; + + $result = UiBloatScanner::scanSource($file, $source); + $allowlisted = UiBloatScanner::scanSource($file, $source, UiBloatScanner::STRICTNESS_WARN, [ + spec375AllowlistEntry(), + ]); + + expect($result['blocking_failures'])->toHaveCount(1) + ->and($result['blocking_failures'][0]['rule_id'])->toBe('UIBLOAT_CUSTOMER_RAW_ID') + ->and($allowlisted['blocking_failures'])->toBeEmpty() + ->and($allowlisted['findings'][0]['result'])->toBe('allowlisted'); +}); + +it('hard-fails customer internal provider or debug terms unless allowlisted', function (): void { + $file = 'apps/platform/resources/views/filament/pages/reviews/customer-review-workspace.blade.php'; + $source = '
Customer review output

Raw Graph payload

'; + + $result = UiBloatScanner::scanSource($file, $source); + $allowlisted = UiBloatScanner::scanSource($file, $source, UiBloatScanner::STRICTNESS_WARN, [ + spec375AllowlistEntry([ + 'rule_id' => 'UIBLOAT_CUSTOMER_INTERNAL_TERM', + 'pattern' => 'raw graph payload', + 'reason' => 'Fixture proves scoped exception behavior.', + ]), + ]); + + expect($result['blocking_failures'])->toHaveCount(1) + ->and($result['blocking_failures'][0]['rule_id'])->toBe('UIBLOAT_CUSTOMER_INTERNAL_TERM') + ->and($allowlisted['blocking_failures'])->toBeEmpty(); +}); + +it('keeps zero metrics and repeated status as non-blocking review findings in warn mode', function (): void { + $source = str_repeat('Status ready lifecycle completed. ', 18).'

0 findings

'; + $result = UiBloatScanner::scanSource('apps/platform/app/Filament/Pages/EnvironmentDashboard.php', $source); + + expect($result['blocking_failures'])->toBeEmpty() + ->and(array_keys($result['summary_by_rule']))->toContain('UIBLOAT_ZERO_METRIC_CARD', 'UIBLOAT_REPEATED_STATUS') + ->and($result['summary_by_result'])->toHaveKey('warning') + ->and($result['summary_by_result'])->toHaveKey('manual-review-required'); +}); + +it('reports decision hierarchy action overload evidence diagnostic mixing and metadata findings as manual review', function (): void { + $pageSource = <<<'PHP' +toContain( + 'UIBLOAT_MISSING_PRIMARY_QUESTION', + 'UIBLOAT_HEADER_ACTION_OVERLOAD', + 'UIBLOAT_EVIDENCE_DIAGNOSTICS_MIXED', + 'UIBLOAT_TECH_METADATA_MAIN', + ) + ->and($pageResult['blocking_failures'])->toBeEmpty() + ->and($metadataResult['blocking_failures'])->toBeEmpty(); +}); + +it('reports diagnostic guidance and ambiguous entrypoint drift as manual review', function (): void { + $source = <<<'BLADE' + +

Diagnostics hub

+

Raw payload provider context debug trace operation id diagnostic details.

+
+BLADE; + + $result = UiBloatScanner::scanSource('apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php', $source); + + expect(array_keys($result['summary_by_rule']))->toContain( + 'UIBLOAT_DIAGNOSTIC_GUIDANCE_MISSING', + 'UIBLOAT_DIAGNOSTIC_ENTRYPOINT_AMBIGUOUS', + ) + ->and($result['blocking_failures'])->toBeEmpty(); +}); + +it('validates allowlist entry shape and rejects blanket entries', function (): void { + $errors = UiBloatScanner::validateAllowlist([ + [ + 'rule_id' => 'UIBLOAT_CUSTOMER_RAW_ID', + 'file' => 'apps/platform/app/Filament', + ], + ]); + + expect($errors)->not->toBeEmpty() + ->and(implode("\n", $errors))->toContain('reason') + ->and(implode("\n", $errors))->toContain('forbidden blanket file pattern'); +}); + +it('excludes non-runtime paths from ui findings by default', function (): void { + expect(UiBloatScanner::isRuntimeUiPath('apps/platform/routes/web.php'))->toBeFalse() + ->and(UiBloatScanner::isRuntimeUiPath('apps/platform/app/Models/User.php'))->toBeFalse() + ->and(UiBloatScanner::isRuntimeUiPath('specs/375-ui-bloat-regression-guard/spec.md'))->toBeFalse() + ->and(UiBloatScanner::isRuntimeUiPath('apps/platform/tests/Feature/Guards/UiBloatRegressionGuardTest.php'))->toBeFalse() + ->and(UiBloatScanner::isRuntimeUiPath('apps/platform/app/Filament/Pages/ExamplePage.php'))->toBeTrue(); +}); + +it('scans the configured runtime ui paths without unallowlisted customer safety blockers', function (): void { + $result = UiBloatScanner::scanConfiguredPaths(repo_path(), UiBloatScanner::STRICTNESS_WARN); + + expect($result['files_scanned'])->toBeGreaterThan(0) + ->and($result['config_errors'])->toBeEmpty() + ->and($result['blocking_failures'])->toBeEmpty( + "Unexpected customer/auditor blocking UI bloat findings:\n".UiBloatScanner::formatFindings($result['blocking_failures']), + ) + ->and($result['absent_candidate_paths']['apps/platform/resources/views/components'])->toBe('not available') + ->and($result['absent_candidate_paths']['apps/platform/app/View'])->toBe('not available'); +}); diff --git a/apps/platform/tests/Pest.php b/apps/platform/tests/Pest.php index 2d497d03..69c7708b 100644 --- a/apps/platform/tests/Pest.php +++ b/apps/platform/tests/Pest.php @@ -112,6 +112,7 @@ 'Feature/Filament/WorkspaceOnlySurfaceTenantIndependenceTest.php', 'Feature/Guards/ActionSurfaceContractTest.php', 'Feature/Guards/OperationLifecycleOpsUxGuardTest.php', + 'Feature/Guards/UiBloatRegressionGuardTest.php', 'Feature/ProviderConnections/CredentialLeakGuardTest.php', 'Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php', 'Feature/Rbac/WorkspaceMembershipsRelationManagerUiEnforcementTest.php', diff --git a/apps/platform/tests/Support/TestLaneManifest.php b/apps/platform/tests/Support/TestLaneManifest.php index 215f68bb..7281aab0 100644 --- a/apps/platform/tests/Support/TestLaneManifest.php +++ b/apps/platform/tests/Support/TestLaneManifest.php @@ -563,6 +563,34 @@ public static function families(): array 'costSignals' => ['route-bounded surface scan', 'start-host governance breadth', 'gate adoption regression detection'], 'validationStatus' => 'guarded', ], + [ + 'familyId' => 'ui-bloat-regression-guard', + 'classificationId' => 'surface-guard', + 'purpose' => 'Guard known UI bloat and customer/auditor safety regression patterns across configured runtime UI source paths.', + 'currentLaneId' => 'heavy-governance', + 'targetLaneId' => 'heavy-governance', + 'selectors' => [ + [ + 'selectorType' => 'group', + 'selectorValue' => 'surface-guard', + 'selectorRole' => 'include', + 'sourceOfTruth' => 'pest-group', + 'rationale' => 'UI bloat scanning spans multiple source surfaces and belongs to heavy governance.', + ], + [ + 'selectorType' => 'file', + 'selectorValue' => 'tests/Feature/Guards/UiBloatRegressionGuardTest.php', + 'selectorRole' => 'inventory-only', + 'sourceOfTruth' => 'manifest', + 'rationale' => 'Spec 375 source-scan guard entrypoint.', + ], + ], + 'hotspotFiles' => [ + 'tests/Feature/Guards/UiBloatRegressionGuardTest.php', + ], + 'costSignals' => ['source-scan guard inventory', 'customer-safety leakage heuristics', 'manual-review finding classification'], + 'validationStatus' => 'guarded', + ], [ 'familyId' => 'policy-resource-admin-search-parity', 'classificationId' => 'discovery-heavy', diff --git a/apps/platform/tests/Support/UiBloat/UiBloatScanner.php b/apps/platform/tests/Support/UiBloat/UiBloatScanner.php new file mode 100644 index 00000000..d5ac343e --- /dev/null +++ b/apps/platform/tests/Support/UiBloat/UiBloatScanner.php @@ -0,0 +1,810 @@ +> $allowlist + * @return array + */ + public static function scanConfiguredPaths( + string $repoRoot, + string $strictness = self::STRICTNESS_WARN, + array $allowlist = [], + ): array { + $files = self::discoverRuntimeUiFiles($repoRoot); + $result = self::scanFiles($repoRoot, $files, $strictness, $allowlist); + + $result['configured_paths'] = self::configuredPathStatus($repoRoot); + $result['absent_candidate_paths'] = self::absentCandidatePathStatus($repoRoot); + + return $result; + } + + /** + * @return list + */ + public static function configuredScanPaths(): array + { + return self::RUNTIME_SCAN_PATHS; + } + + /** + * @return list + */ + public static function absentCandidatePaths(): array + { + return self::ABSENT_CANDIDATE_PATHS; + } + + /** + * @return list + */ + public static function discoverRuntimeUiFiles(string $repoRoot): array + { + $files = []; + + foreach (self::RUNTIME_SCAN_PATHS as $relativeRoot) { + $absoluteRoot = self::normalizePath($repoRoot.'/'.$relativeRoot); + + if (! is_dir($absoluteRoot)) { + continue; + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($absoluteRoot, FilesystemIterator::SKIP_DOTS) + ); + + /** @var SplFileInfo $file */ + foreach ($iterator as $file) { + if (! $file->isFile()) { + continue; + } + + $path = self::normalizePath($file->getPathname()); + + if (! self::isRuntimeUiPath(self::relativePath($repoRoot, $path))) { + continue; + } + + $files[] = $path; + } + } + + sort($files); + + return array_values(array_unique($files)); + } + + public static function isRuntimeUiPath(string $relativePath): bool + { + $relativePath = self::normalizePath($relativePath); + + if (! str_ends_with($relativePath, '.php')) { + return false; + } + + foreach ([ + 'vendor/', + 'node_modules/', + 'storage/', + 'bootstrap/cache/', + 'public/build/', + 'database/', + 'specs/', + 'tests/', + 'translations/', + 'lang/', + ] as $excludedPrefix) { + if (str_starts_with($relativePath, $excludedPrefix)) { + return false; + } + } + + foreach (self::RUNTIME_SCAN_PATHS as $scanPath) { + if (str_starts_with($relativePath, $scanPath.'/')) { + return true; + } + } + + return false; + } + + /** + * @param list $absoluteFiles + * @param list> $allowlist + * @return array + */ + public static function scanFiles( + string $repoRoot, + array $absoluteFiles, + string $strictness = self::STRICTNESS_WARN, + array $allowlist = [], + ): array { + $findings = []; + $configErrors = self::validateAllowlist($allowlist); + + foreach ($absoluteFiles as $absoluteFile) { + $relativePath = self::relativePath($repoRoot, $absoluteFile); + + if (! self::isRuntimeUiPath($relativePath)) { + continue; + } + + $source = file_get_contents($absoluteFile); + + if (! is_string($source) || $source === '') { + continue; + } + + $fileResult = self::scanSource($relativePath, $source, $strictness, $allowlist); + $findings = array_merge($findings, $fileResult['findings']); + } + + return self::result($strictness, count($absoluteFiles), $findings, $configErrors); + } + + /** + * @param list> $allowlist + * @return array + */ + public static function scanSource( + string $relativePath, + string $source, + string $strictness = self::STRICTNESS_WARN, + array $allowlist = [], + ): array { + $findings = self::findingsForSource($relativePath, $source); + $findings = array_map( + static fn (array $finding): array => self::applyAllowlist($finding, $allowlist), + $findings, + ); + + return self::result($strictness, 1, $findings, self::validateAllowlist($allowlist)); + } + + /** + * @param list> $allowlist + * @return list + */ + public static function validateAllowlist(array $allowlist): array + { + $errors = []; + + foreach ($allowlist as $index => $entry) { + foreach (self::ALLOWLIST_REQUIRED_KEYS as $key) { + if (! isset($entry[$key]) || trim((string) $entry[$key]) === '') { + $errors[] = sprintf('Allowlist entry %d is missing required key [%s].', $index, $key); + } + } + + $file = (string) ($entry['file'] ?? ''); + + if (in_array($file, self::RUNTIME_SCAN_PATHS, true) || str_ends_with($file, '/*')) { + $errors[] = sprintf('Allowlist entry %d uses a forbidden blanket file pattern [%s].', $index, $file); + } + } + + return $errors; + } + + /** + * @param list> $findings + */ + public static function formatFindings(array $findings): string + { + if ($findings === []) { + return 'No findings.'; + } + + return implode("\n", array_map( + static fn (array $finding): string => sprintf( + '%s %s %s:%d [%s] %s', + $finding['rule_id'], + strtoupper((string) $finding['result']), + $finding['file'], + $finding['line'], + $finding['surface'], + $finding['reason'], + ), + $findings, + )); + } + + /** + * @return list> + */ + private static function findingsForSource(string $relativePath, string $source): array + { + $lowerSource = strtolower($source); + $surface = self::classifySurface($relativePath, $source); + $findings = []; + + foreach (self::CUSTOMER_RAW_ID_TERMS as $term) { + foreach (self::termOffsets($lowerSource, $term) as $offset) { + $technicalContext = self::isTechnicalContext($lowerSource, $offset); + $customerDefault = $surface === 'customer-auditor' && ! $technicalContext; + + $findings[] = self::finding( + 'UIBLOAT_CUSTOMER_RAW_ID', + $relativePath, + $term, + $surface, + $customerDefault ? 'blocking' : 'manual-review-required', + $customerDefault ? 'blocker' : 'medium', + $customerDefault + ? 'Raw ID label appears in a likely customer/auditor default surface.' + : 'Raw ID label appears in a non-customer or technical-detail context and needs review.', + 'Move raw IDs behind collapsed technical/support detail or document an allowlist exception.', + self::lineForOffset($source, $offset), + ); + } + } + + foreach (self::CUSTOMER_INTERNAL_TERMS as $term) { + foreach (self::termOffsets($lowerSource, $term) as $offset) { + $technicalContext = self::isTechnicalContext($lowerSource, $offset); + $customerDefault = $surface === 'customer-auditor' && ! $technicalContext; + + $findings[] = self::finding( + 'UIBLOAT_CUSTOMER_INTERNAL_TERM', + $relativePath, + $term, + $surface, + $customerDefault ? 'blocking' : 'manual-review-required', + $customerDefault ? 'blocker' : 'medium', + $customerDefault + ? 'Internal/debug/provider term appears in a likely customer/auditor default surface.' + : 'Internal/debug/provider term appears outside a hard customer default context and needs review.', + 'Reword customer copy or move internal detail to diagnostics/technical disclosure.', + self::lineForOffset($source, $offset), + ); + } + } + + if (preg_match_all('/\b0\s+(?:findings|alerts|failures|errors|runs|reviews|items|policies|backups|reports|snapshots|issues)\b/i', $source, $matches, PREG_OFFSET_CAPTURE)) { + foreach ($matches[0] as [$match, $offset]) { + $findings[] = self::finding( + 'UIBLOAT_ZERO_METRIC_CARD', + $relativePath, + (string) $match, + $surface, + 'warning', + 'low', + 'Zero metric copy can create no-action dashboard noise.', + 'Suppress zero-only cards unless zero is itself proof or audit-significant.', + self::lineForOffset($source, (int) $offset), + ); + } + } + + $statusCount = preg_match_all('/\b(?:status|state|ready|readiness|lifecycle|pending|completed|failed|blocked|available|unavailable)\b/i', $source); + + if ($statusCount >= 16) { + $findings[] = self::finding( + 'UIBLOAT_REPEATED_STATUS', + $relativePath, + 'status/readiness/lifecycle repeated '.$statusCount.' times', + $surface, + 'manual-review-required', + 'medium', + 'Status or lifecycle language appears repeatedly in one source file.', + 'Keep one first-viewport owner for status/reason/impact and let lower sections add evidence.', + 1, + ); + } + + if (self::looksLikePage($relativePath, $source) && ! self::hasPrimaryQuestionMarker($lowerSource)) { + $findings[] = self::finding( + 'UIBLOAT_MISSING_PRIMARY_QUESTION', + $relativePath, + 'no primary question marker', + $surface, + 'manual-review-required', + 'medium', + 'Page-like source lacks an obvious primary question, outcome, or next-action marker.', + 'Add or preserve decision-first copy and one clear next action where the rendered surface needs it.', + 1, + ); + } + + if (self::headerActionCount($source) >= 5) { + $findings[] = self::finding( + 'UIBLOAT_HEADER_ACTION_OVERLOAD', + $relativePath, + 'five or more header actions', + $surface, + 'manual-review-required', + 'medium', + 'Header action count may overload the primary next action.', + 'Keep one dominant primary action and group rare or risky actions.', + self::lineForNeedle($source, 'getHeaderActions'), + ); + } + + foreach (['evidence diagnostics', 'diagnostic evidence', 'proof diagnostics', 'diagnostics evidence'] as $term) { + foreach (self::termOffsets($lowerSource, $term) as $offset) { + $findings[] = self::finding( + 'UIBLOAT_EVIDENCE_DIAGNOSTICS_MIXED', + $relativePath, + $term, + $surface, + 'manual-review-required', + 'medium', + 'Evidence and diagnostics language appears mixed.', + 'Separate evidence proof from diagnostics or support explanation.', + self::lineForOffset($source, $offset), + ); + } + } + + foreach (['normalization lineage', 'source refs', 'provider details', 'operation context', 'raw payload'] as $term) { + foreach (self::termOffsets($lowerSource, $term) as $offset) { + if (self::isTechnicalContext($lowerSource, $offset)) { + continue; + } + + $findings[] = self::finding( + 'UIBLOAT_TECH_METADATA_MAIN', + $relativePath, + $term, + $surface, + 'manual-review-required', + 'medium', + 'Technical metadata appears outside an obvious technical-details context.', + 'Move implementation metadata into collapsed technical/support disclosure.', + self::lineForOffset($source, $offset), + ); + } + } + + if ($surface === 'diagnostic-support' && self::technicalTermCount($lowerSource) >= 4 && ! self::hasDiagnosticGuidance($lowerSource)) { + $findings[] = self::finding( + 'UIBLOAT_DIAGNOSTIC_GUIDANCE_MISSING', + $relativePath, + 'diagnostic technicality without guidance', + $surface, + 'manual-review-required', + 'medium', + 'Diagnostic/support source has technical detail without a clear recommended first check.', + 'Add guidance-first wording or document why another surface owns guidance.', + 1, + ); + } + + foreach (['all diagnostics', 'diagnostics hub', 'complete diagnostics', 'full diagnostics', 'environment health diagnostics'] as $term) { + foreach (self::termOffsets($lowerSource, $term) as $offset) { + $findings[] = self::finding( + 'UIBLOAT_DIAGNOSTIC_ENTRYPOINT_AMBIGUOUS', + $relativePath, + $term, + $surface, + 'manual-review-required', + 'medium', + 'Diagnostic entrypoint copy may imply broader coverage than the route owns.', + 'Name support diagnostics, repair diagnostics, or provider readiness explicitly.', + self::lineForOffset($source, $offset), + ); + } + } + + return $findings; + } + + private static function classifySurface(string $relativePath, string $source): string + { + $normalizedPath = strtolower(self::normalizePath($relativePath)); + $combined = $normalizedPath.' '.strtolower($source); + + foreach ([ + 'supportdiagnostics', + 'support-diagnostic', + 'diagnostics', + 'diagnostic', + 'requiredpermissions', + 'required-permissions', + 'providerconnection', + 'provider-connection', + 'providerreadiness', + 'provider-readiness', + ] as $pathMarker) { + if (str_contains($normalizedPath, $pathMarker)) { + return 'diagnostic-support'; + } + } + + foreach ([ + 'customer-review-workspace', + 'customerreviewworkspace', + 'reviewpackresource', + 'storedreportresource', + 'stored-report', + 'environmentreviewresource', + 'evidencesnapshotresource', + 'evidence-snapshot', + ] as $pathMarker) { + if (str_contains($normalizedPath, $pathMarker)) { + return 'customer-auditor'; + } + } + + foreach ([ + 'customer review', + 'auditor', + 'review output', + ] as $marker) { + if (str_contains($combined, $marker)) { + return 'customer-auditor'; + } + } + + if (str_contains($relativePath, 'apps/platform/app/Filament') || str_contains($relativePath, 'apps/platform/resources/views/filament')) { + return 'operator'; + } + + return 'unknown'; + } + + /** + * @return list + */ + private static function termOffsets(string $lowerSource, string $term): array + { + $offsets = []; + $offset = 0; + + while (($position = strpos($lowerSource, strtolower($term), $offset)) !== false) { + $offsets[] = $position; + $offset = $position + max(1, strlen($term)); + } + + return $offsets; + } + + private static function isTechnicalContext(string $lowerSource, int $offset): bool + { + $window = substr($lowerSource, max(0, $offset - 700), 1400); + + foreach ([ + 'technical details', + 'technical detail', + 'technical evidence', + 'collapsed', + 'collapsible', + 'toggledhiddenbydefault', + 'hidden(fn', + 'hidden(', + ' + */ + private static function finding( + string $ruleId, + string $file, + string $pattern, + string $surface, + string $result, + string $severity, + string $reason, + string $suggestedAction, + int $line, + ): array { + return [ + 'rule_id' => $ruleId, + 'file' => self::normalizePath($file), + 'pattern' => $pattern, + 'surface' => $surface, + 'result' => $result, + 'severity' => $severity, + 'reason' => $reason, + 'suggested_action' => $suggestedAction, + 'allowlisted' => false, + 'line' => $line, + ]; + } + + /** + * @param list> $allowlist + * @return array + */ + private static function applyAllowlist(array $finding, array $allowlist): array + { + foreach ($allowlist as $entry) { + if (($entry['rule_id'] ?? null) !== $finding['rule_id']) { + continue; + } + + if (($entry['file'] ?? null) !== $finding['file']) { + continue; + } + + $entryPattern = strtolower((string) ($entry['pattern'] ?? '')); + $findingPattern = strtolower((string) $finding['pattern']); + + if ($entryPattern === '' || ! str_contains($findingPattern, $entryPattern)) { + continue; + } + + $finding['result'] = 'allowlisted'; + $finding['severity'] = 'info'; + $finding['allowlisted'] = true; + $finding['reason'] = 'Allowlisted: '.$entry['reason']; + + return $finding; + } + + return $finding; + } + + /** + * @param list> $findings + * @param list $configErrors + * @return array + */ + private static function result(string $strictness, int $filesScanned, array $findings, array $configErrors): array + { + $strictness = in_array($strictness, [self::STRICTNESS_REPORT, self::STRICTNESS_WARN, self::STRICTNESS_FAIL], true) + ? $strictness + : self::STRICTNESS_WARN; + + $blockingFailures = array_values(array_filter($findings, static function (array $finding) use ($strictness): bool { + if ($finding['allowlisted'] ?? false) { + return false; + } + + return match ($strictness) { + self::STRICTNESS_REPORT => false, + self::STRICTNESS_FAIL => true, + default => $finding['result'] === 'blocking', + }; + })); + + return [ + 'strictness' => $strictness, + 'files_scanned' => $filesScanned, + 'findings' => array_values($findings), + 'blocking_failures' => $blockingFailures, + 'config_errors' => $configErrors, + 'summary_by_rule' => self::summaryBy($findings, 'rule_id'), + 'summary_by_result' => self::summaryBy($findings, 'result'), + 'summary_by_surface' => self::summaryBy($findings, 'surface'), + ]; + } + + /** + * @param list> $findings + * @return array + */ + private static function summaryBy(array $findings, string $key): array + { + $summary = []; + + foreach ($findings as $finding) { + $value = (string) ($finding[$key] ?? 'unknown'); + $summary[$value] = ($summary[$value] ?? 0) + 1; + } + + ksort($summary); + + return $summary; + } + + /** + * @return array + */ + private static function configuredPathStatus(string $repoRoot): array + { + $status = []; + + foreach (self::RUNTIME_SCAN_PATHS as $path) { + $status[$path] = is_dir($repoRoot.'/'.$path) ? 'available' : 'not available'; + } + + return $status; + } + + /** + * @return array + */ + private static function absentCandidatePathStatus(string $repoRoot): array + { + $status = []; + + foreach (self::ABSENT_CANDIDATE_PATHS as $path) { + $status[$path] = is_dir($repoRoot.'/'.$path) ? 'available' : 'not available'; + } + + return $status; + } + + private static function lineForNeedle(string $source, string $needle): int + { + $offset = strpos($source, $needle); + + return self::lineForOffset($source, $offset === false ? 0 : $offset); + } + + private static function lineForOffset(string $source, int $offset): int + { + return substr_count(substr($source, 0, max(0, $offset)), "\n") + 1; + } + + private static function relativePath(string $repoRoot, string $absolutePath): string + { + $repoRoot = rtrim(self::normalizePath($repoRoot), '/'); + $absolutePath = self::normalizePath($absolutePath); + + if (str_starts_with($absolutePath, $repoRoot.'/')) { + return substr($absolutePath, strlen($repoRoot) + 1); + } + + return $absolutePath; + } + + private static function normalizePath(string $path): string + { + return str_replace('\\', '/', $path); + } +} diff --git a/specs/375-ui-bloat-regression-guard/artifacts/affected-files.md b/specs/375-ui-bloat-regression-guard/artifacts/affected-files.md new file mode 100644 index 00000000..8684544a --- /dev/null +++ b/specs/375-ui-bloat-regression-guard/artifacts/affected-files.md @@ -0,0 +1,18 @@ +# Affected Files + +| File | Purpose | Change Type | Classification | Risk | Verification | +|---|---|---|---|---|---| +| `specs/375-ui-bloat-regression-guard/artifacts/source-summary.md` | Source inventory and implementation option record | add | spec artifact | low | review | +| `specs/375-ui-bloat-regression-guard/artifacts/guard-rules.md` | Rule contract | add | spec artifact | low | review | +| `specs/375-ui-bloat-regression-guard/artifacts/scanner-design.md` | Scanner design | add | spec artifact | low | review | +| `specs/375-ui-bloat-regression-guard/artifacts/allowlist-policy.md` | Allowlist policy | add | spec artifact | low | review | +| `specs/375-ui-bloat-regression-guard/artifacts/initial-scan-report.md` | Initial scan evidence | add | spec artifact | medium | guard run | +| `specs/375-ui-bloat-regression-guard/artifacts/validation-report.md` | Validation closeout | add | spec artifact | low | commands recorded | +| `specs/375-ui-bloat-regression-guard/artifacts/follow-up-recommendations.md` | Follow-up scope | add | spec artifact | low | review | +| `apps/platform/tests/Support/UiBloat/UiBloatScanner.php` | Test-owned source scanner | add | test support | medium | Pest guard | +| `apps/platform/tests/Feature/Guards/UiBloatRegressionGuardTest.php` | Guard entrypoint and behavior coverage | add | heavy-governance test | medium | targeted Pest | +| `apps/platform/tests/Pest.php` | Surface-guard group registration | update | test config | low | targeted Pest/manifest | +| `apps/platform/tests/Support/TestLaneManifest.php` | Heavy-governance family ownership | update | test governance | low | manifest tests | +| `specs/375-ui-bloat-regression-guard/tasks.md` | Completion state | update | spec artifact | low | review | + +No runtime UI, route, model, migration, policy, provider, Graph, queue, scheduler, storage, or Filament panel files are intentionally changed. diff --git a/specs/375-ui-bloat-regression-guard/artifacts/allowlist-policy.md b/specs/375-ui-bloat-regression-guard/artifacts/allowlist-policy.md new file mode 100644 index 00000000..35662e91 --- /dev/null +++ b/specs/375-ui-bloat-regression-guard/artifacts/allowlist-policy.md @@ -0,0 +1,57 @@ +# Allowlist Policy + +## V1 Decision + +Spec 375 does not introduce a committed allowlist file. The current repo scan runs with an empty allowlist and documents warnings/manual-review findings in `initial-scan-report.md`. + +Future allowlist storage, if needed: + +- Path: `specs/375-ui-bloat-regression-guard/artifacts/ui-bloat-allowlist.json` +- Format: JSON array of scoped entries. + +## Required Entry Shape + +Each future entry must contain: + +- `rule_id` +- `file` +- `pattern` +- `reason` +- `surface_type` +- `audience` +- `review_marker` +- `expires_or_review_after` +- `owner_spec` + +## Allowed Reasons + +- Known existing debt documented in active or follow-up spec. +- False positive from collapsed technical details. +- Provider-owned or diagnostic-owned surface where the term is required. +- Temporary manual-review exception with owner and review date. + +## Forbidden Patterns + +- Blanket allowlist for `apps/platform/app/Filament`. +- Blanket allowlist for customer/auditor surfaces. +- Rule-wide allowlist with no file and pattern. +- Entries without owner, reason, review marker, or expiry/review date. +- Entries that hide a clear customer/auditor default-surface raw ID/internal-term leak without remediation or approved follow-up. + +## Example + +```json +[ + { + "rule_id": "UIBLOAT_CUSTOMER_RAW_ID", + "file": "apps/platform/resources/views/filament/pages/reviews/example.blade.php", + "pattern": "operation id", + "reason": "Appears only inside a collapsed technical details section.", + "surface_type": "customer-auditor", + "audience": "operator-support", + "review_marker": "manual-review-required", + "expires_or_review_after": "2026-09-01", + "owner_spec": "specs/375-ui-bloat-regression-guard" + } +] +``` diff --git a/specs/375-ui-bloat-regression-guard/artifacts/follow-up-recommendations.md b/specs/375-ui-bloat-regression-guard/artifacts/follow-up-recommendations.md new file mode 100644 index 00000000..cab1d127 --- /dev/null +++ b/specs/375-ui-bloat-regression-guard/artifacts/follow-up-recommendations.md @@ -0,0 +1,23 @@ +# Follow-Up Recommendations + +## CI Strictness + +- Keep Spec 375 v1 as warn-first and heavy-governance-owned. +- Consider CI hard-fail expansion only after allowlist cleanup and at least one stable maintenance cycle. +- Candidate future hard-fail additions: repeated raw/internal customer-surface leaks, missing primary question on customer/auditor defaults, and ambiguous diagnostic entrypoints that misroute users to repair-only surfaces. + +## Manual Review Leftovers + +- `UIBLOAT_ZERO_METRIC_CARD`, `UIBLOAT_REPEATED_STATUS`, `UIBLOAT_HEADER_ACTION_OVERLOAD`, and diagnostic guidance rules should remain manual-review or warning by default until false-positive behavior is understood. + +## Evidence/System Browser Fixtures + +- Evidence and system surface browser fixture coverage remains a separate follow-up candidate. Spec 375 does not add browser coverage. + +## Browser Scorecard Integration + +- Integrating guard output with browser scorecards is deferred. V1 keeps source scanning and browser scoring separate. + +## Post-Productization Closeout Audit + +- A final browser closeout audit after guard stabilization remains useful, but it should be a separate spec because it would add browser lane cost and screenshot review scope. diff --git a/specs/375-ui-bloat-regression-guard/artifacts/guard-rules.md b/specs/375-ui-bloat-regression-guard/artifacts/guard-rules.md new file mode 100644 index 00000000..612f98cb --- /dev/null +++ b/specs/375-ui-bloat-regression-guard/artifacts/guard-rules.md @@ -0,0 +1,38 @@ +# Guard Rules + +Default strictness is `warn`. Ambiguous heuristics are `manual-review-required`. +Only clear customer/auditor default-surface leakage is blocking in v1. + +| Rule ID | Purpose | Default Result | Blocking In V1 | Allowlist | +|---|---|---|---|---| +| `UIBLOAT_ZERO_METRIC_CARD` | Detect zero-card spam that makes no-action pages noisy. | warning | no | allowed with scoped reason | +| `UIBLOAT_REPEATED_STATUS` | Detect repeated lifecycle/status/readiness wording. | manual-review-required | no | allowed with scoped reason | +| `UIBLOAT_CUSTOMER_RAW_ID` | Detect raw ID labels on likely customer/auditor defaults. | blocking on customer/auditor; manual review elsewhere | yes | allowed only with explicit technical-details rationale | +| `UIBLOAT_CUSTOMER_INTERNAL_TERM` | Detect internal/debug/provider terms on likely customer/auditor defaults. | blocking on customer/auditor; manual review elsewhere | yes | allowed only with explicit technical-details rationale | +| `UIBLOAT_MISSING_PRIMARY_QUESTION` | Detect pages without a clear question/next-action marker. | manual-review-required | no | allowed with scoped reason | +| `UIBLOAT_HEADER_ACTION_OVERLOAD` | Detect many header actions competing for attention. | manual-review-required | no | allowed with workflow-hub rationale | +| `UIBLOAT_EVIDENCE_DIAGNOSTICS_MIXED` | Detect copy that blurs proof/evidence and diagnostics. | manual-review-required | no | allowed with support-surface rationale | +| `UIBLOAT_TECH_METADATA_MAIN` | Detect technical metadata in main/default content. | manual-review-required | no | allowed with diagnostic/system-surface rationale | +| `UIBLOAT_DIAGNOSTIC_GUIDANCE_MISSING` | Detect diagnostic-heavy files without guidance markers. | manual-review-required | no | allowed with external guidance rationale | +| `UIBLOAT_DIAGNOSTIC_ENTRYPOINT_AMBIGUOUS` | Detect broad diagnostic labels for repair-only/support-only routes. | manual-review-required | no | allowed with named-entrypoint rationale | + +## Pattern Examples + +- Raw ID labels: `operation id`, `workspace id`, `tenant id`, `provider object id`, `fingerprint`. +- Internal terms: `operation context`, `raw graph payload`, `debug`, `stack trace`, `provider response body`, `internal reason`. +- Guidance markers: `recommended first check`, `start here`, `next check`, `use this when`, `repair diagnostics`, `support diagnostics`. +- Evidence/diagnostics mixing markers: `evidence diagnostics`, `diagnostic evidence`, `proof diagnostics`. + +## Strictness + +- `report`: returns findings, no blocking exit. +- `warn`: returns findings and marks only hard customer/auditor safety findings as blocking. +- `fail`: treats all warning/manual-review/blocking findings as failing for explicit future hardening. + +## Suggested Actions + +- Move raw/internal/provider detail behind collapsed technical details. +- Add or clarify the primary operator/customer question. +- Group secondary header actions. +- Separate evidence proof from diagnostics. +- Add diagnostic guidance or rename ambiguous entrypoints. diff --git a/specs/375-ui-bloat-regression-guard/artifacts/initial-scan-report.md b/specs/375-ui-bloat-regression-guard/artifacts/initial-scan-report.md new file mode 100644 index 00000000..447915e2 --- /dev/null +++ b/specs/375-ui-bloat-regression-guard/artifacts/initial-scan-report.md @@ -0,0 +1,89 @@ +# Initial Scan Report + +## Command + +Executed in Sail with the test-owned scanner: + +```bash +cd apps/platform && ./vendor/bin/sail php -r 'require "vendor/autoload.php"; $result = Tests\Support\UiBloat\UiBloatScanner::scanConfiguredPaths("/var/www/repo", Tests\Support\UiBloat\UiBloatScanner::STRICTNESS_WARN); ...' +``` + +## Summary + +| Metric | Result | +|---|---:| +| Files scanned | 417 | +| Blocking failures | 0 | +| Warnings | 24 | +| Manual-review findings | 346 | +| Allowlisted findings | 0 | +| False positives classified in v1 | 0 | + +V1 produced no unallowlisted hard customer/auditor safety failures. Existing findings are review signals and known-debt candidates only; no broad UI refactor is in scope. + +## Findings By Rule + +| Rule | Count | V1 Treatment | +|---|---:|---| +| `UIBLOAT_CUSTOMER_INTERNAL_TERM` | 28 | manual review unless customer-default hard leak | +| `UIBLOAT_CUSTOMER_RAW_ID` | 120 | manual review unless customer-default hard leak | +| `UIBLOAT_DIAGNOSTIC_GUIDANCE_MISSING` | 15 | manual review | +| `UIBLOAT_EVIDENCE_DIAGNOSTICS_MIXED` | 1 | manual review | +| `UIBLOAT_HEADER_ACTION_OVERLOAD` | 12 | manual review | +| `UIBLOAT_MISSING_PRIMARY_QUESTION` | 99 | manual review | +| `UIBLOAT_REPEATED_STATUS` | 60 | manual review | +| `UIBLOAT_TECH_METADATA_MAIN` | 11 | manual review | +| `UIBLOAT_ZERO_METRIC_CARD` | 24 | warning | + +## Findings By Surface Classification + +| Surface Classification | Count | +|---|---:| +| customer-auditor | 34 | +| diagnostic-support | 49 | +| operator | 246 | +| unknown | 41 | + +## Highest-Volume Files + +| File | Count | Treatment | +|---|---:|---| +| `apps/platform/app/Support/SupportDiagnostics/SupportDiagnosticBundleBuilder.php` | 16 | diagnostic-support manual review | +| `apps/platform/app/Support/Ui/DerivedState/RequestScopedDerivedStateStore.php` | 14 | unknown/operator manual review | +| `apps/platform/app/Filament/Support/VerificationReportViewer.php` | 12 | operator/manual review | +| `apps/platform/app/Filament/Resources/FindingResource.php` | 10 | operator/manual review | +| `apps/platform/app/Filament/Resources/OperationRunResource.php` | 10 | operator/manual review | +| `apps/platform/app/Filament/Resources/RestoreRunResource.php` | 10 | operator/manual review | +| `apps/platform/app/Support/Ui/GovernanceArtifactTruth/ArtifactTruthPresenter.php` | 10 | support/UI helper manual review | +| `apps/platform/app/Filament/Pages/Workspaces/ManagedEnvironmentOnboardingWizard.php` | 9 | operator/manual review | +| `apps/platform/app/Filament/Support/VerificationReportChangeIndicator.php` | 8 | operator/manual review | +| `apps/platform/app/Filament/Resources/ReviewPackResource.php` | 6 | customer/auditor technical-detail manual review | + +## Blocking Failures + +None. + +## Warnings + +The 24 warning findings are `UIBLOAT_ZERO_METRIC_CARD` matches. They are review signals only in v1. + +## Manual-Review Findings + +Manual-review findings are intentionally retained as review evidence. They cover raw IDs in technical/collapsed contexts, repeated status language, header action count, missing primary question markers, technical metadata, and diagnostic guidance ambiguity. + +## Allowlisted Findings + +None. Spec 375 v1 does not commit an allowlist file. + +## Known Existing Debt + +- Broad source-level status/readiness repetition remains visible in several operator resources. +- Diagnostic/support source files contain technical terms that are expected but should remain guidance-first in rendered surfaces. +- Some customer/auditor resources still contain raw IDs or fingerprints in hidden/collapsed technical areas; these remain manual-review findings rather than hard failures. + +## Recommended Follow-Ups + +- Re-run this guard after the next UI surface change and compare counts. +- Consider a future allowlist file only if manual-review findings become noisy. +- Defer CI hard-fail expansion until allowlist cleanup. +- Keep browser-scorecard integration separate from this v1 guard. diff --git a/specs/375-ui-bloat-regression-guard/artifacts/scanner-design.md b/specs/375-ui-bloat-regression-guard/artifacts/scanner-design.md new file mode 100644 index 00000000..04706def --- /dev/null +++ b/specs/375-ui-bloat-regression-guard/artifacts/scanner-design.md @@ -0,0 +1,72 @@ +# Scanner Design + +## Entrypoint + +- Selected entrypoint: `apps/platform/tests/Feature/Guards/UiBloatRegressionGuardTest.php` +- Helper: `apps/platform/tests/Support/UiBloat/UiBloatScanner.php` +- Command: `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards/UiBloatRegressionGuardTest.php` + +## File Discovery + +The scanner reads configured source paths only: + +- `apps/platform/app/Filament` +- `apps/platform/resources/views/filament` +- `apps/platform/app/Support/EnvironmentDashboard` +- `apps/platform/app/Support/Navigation` +- `apps/platform/app/Support/OpsUx` +- `apps/platform/app/Support/SupportDiagnostics` +- `apps/platform/app/Support/Ui` +- `apps/platform/app/Support/Workspaces` + +Absent optional paths are recorded, not scanned: + +- `apps/platform/resources/views/components` +- `apps/platform/app/View` + +The scanner does not scan `apps/platform/app/Support` wholesale. + +## Extensions + +- Runtime source extensions: `.php`, `.blade.php` +- Fixture strings: accepted directly by tests to prove rule behavior. + +## Exclusions + +The scanner excludes vendor, node modules, storage, build artifacts, generated reports, screenshots, specs, tests except explicit fixtures, translations, database dumps, and hidden cache paths. + +## Surface Classification + +- `customer-auditor`: path/content contains customer review, review pack, stored report, environment review, evidence snapshot, auditor, customer, review output, or review handoff markers. +- `diagnostic-support`: path/content contains diagnostics, support diagnostics, repair diagnostics, required permissions, provider readiness, or support-only markers. +- `operator`: Filament/admin/support UI source without customer/auditor or diagnostic-support markers. +- `unknown`: files outside configured UI source conventions. + +## Finding Shape + +Each finding contains: + +- rule ID +- file +- matched pattern +- surface classification +- result +- severity +- reason +- suggested action +- allowlist status + +## Allowlist + +V1 uses an in-test empty allowlist for current repo scanning and fixture tests. No committed allowlist file is introduced in v1. The policy for a future file is documented in `allowlist-policy.md`. + +## Exit Behavior + +The Pest guard asserts that the current repo scan in `warn` mode has no unallowlisted blocking findings. Warning and manual-review findings are captured in `initial-scan-report.md` and do not fail v1 unless `fail` strictness is intentionally selected in tests. + +## Limitations + +- Text heuristics do not prove rendered DOM visibility. +- Collapsed technical-details detection is conservative and may still mark a raw/internal customer match as manual review. +- Header action overload is source-shape based and intentionally manual-review only. +- No browser, screenshot, or accessibility proof is included. diff --git a/specs/375-ui-bloat-regression-guard/artifacts/source-summary.md b/specs/375-ui-bloat-regression-guard/artifacts/source-summary.md new file mode 100644 index 00000000..c854e18c --- /dev/null +++ b/specs/375-ui-bloat-regression-guard/artifacts/source-summary.md @@ -0,0 +1,60 @@ +# Source Summary + +## Repo State + +- Branch: `375-ui-bloat-regression-guard` +- HEAD before implementation: `0a1ecf99` +- Dirty state before implementation: untracked Spec 375 artifacts only. +- Implementation option selected: Pest guard under `apps/platform/tests/Feature/Guards/UiBloatRegressionGuardTest.php`. +- Rejected options: + - Artisan command: broader than v1 because it adds a runtime app command surface for a review guard. + - Repo shell script: less repo-conform than existing Pest guard conventions and harder to test with fixture cases. + +## Source Inputs + +| Source | Status | Notes | +|---|---|---| +| `specs/368-platform-ui-signal-to-noise-browser-audit/audit.md` | available | Source audit input for bloat and customer-safety failure modes. | +| `specs/368-platform-ui-signal-to-noise-browser-audit/page-scorecard.csv` | available | Score context only; not used as a scanner threshold in v1. | +| `specs/368-platform-ui-signal-to-noise-browser-audit/findings.md` | available | Source for repeated status, zero-card, metadata, and customer-safe risks. | +| `specs/368-platform-ui-signal-to-noise-browser-audit/spec-candidates.md` | available | Confirms guardrail candidate lineage. | +| `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/raw/browser-notes.md` | available | Browser notes input; no browser dependency in Spec 375. | +| `specs/370-global-surface-information-architecture-contract/artifacts/surface-contract.md` | available | Surface IA contract input. | +| `specs/370-global-surface-information-architecture-contract/artifacts/ui-bloat-patterns.md` | available | Primary rule-group source for v1. | +| `specs/370-global-surface-information-architecture-contract/artifacts/page-assessment-checklist.md` | available | Manual review checklist source. | +| `specs/370-global-surface-information-architecture-contract/artifacts/copy-and-terminology-rules.md` | available | Customer-safe and provider/internal term source. | +| `specs/370-global-surface-information-architecture-contract/artifacts/follow-up-spec-map.md` | available | Confirms guardrail follow-up lineage. | +| `specs/371-core-operator-view-surfaces-productization/artifacts/*` | available | Completed operator-surface context, read-only. | +| `specs/372-customer-auditor-surface-safety-pass/artifacts/*` | available | Customer/auditor safety contracts and browser evidence, read-only. | +| `specs/373-diagnostic-surface-separation/artifacts/*` | available | Actual repo directory differs from draft name; diagnostic safety contracts are available. | +| `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/*` | available | Diagnostic entrypoint matrix and validation evidence, read-only. | + +## Existing Guard Structures + +- Existing guard tests live under `apps/platform/tests/Feature/Guards`. +- `apps/platform/tests/Pest.php` defines the `surface-guard` group. +- `apps/platform/tests/Support/TestLaneManifest.php` declares `surface-guard` as heavy-governance-only. +- `scripts/check-ui-productization-coverage` is a repo-level shell guard for UI coverage acknowledgements, not content-quality scanning. +- `apps/platform/tests/Support/OpsUx/SourceFileScanner.php` exists for PHP-only source scans, but Spec 375 also needs Blade scanning and fixture-string scans, so a narrow Spec 375 test helper is used. + +## Scan Paths + +| Path | Status | Treatment | +|---|---|---| +| `apps/platform/app/Filament` | available | scanned | +| `apps/platform/resources/views/filament` | available | scanned | +| `apps/platform/app/Support/EnvironmentDashboard` | available | scanned | +| `apps/platform/app/Support/Navigation` | available | scanned | +| `apps/platform/app/Support/OpsUx` | available | scanned | +| `apps/platform/app/Support/SupportDiagnostics` | available | scanned | +| `apps/platform/app/Support/Ui` | available | scanned | +| `apps/platform/app/Support/Workspaces` | available | scanned | +| `apps/platform/resources/views/components` | not available | recorded only | +| `apps/platform/app/View` | not available | recorded only | + +## Lane Ownership + +- Classification: Heavy-Governance / `surface-guard`. +- The guard is registered as a surface-guard Pest file and as a heavy-governance manifest family. +- The test uses repository source text and fixture strings only. It does not require browser auth, seed data, provider setup, Graph calls, queues, or runtime product state. +- Because the repository binds `Feature` tests to `RefreshDatabase` globally, the file still boots the normal Feature test harness, but the guard introduces no database fixture or data dependency. diff --git a/specs/375-ui-bloat-regression-guard/artifacts/validation-report.md b/specs/375-ui-bloat-regression-guard/artifacts/validation-report.md new file mode 100644 index 00000000..4a6b177d --- /dev/null +++ b/specs/375-ui-bloat-regression-guard/artifacts/validation-report.md @@ -0,0 +1,55 @@ +# Validation Report + +## Initial State + +- Branch: `375-ui-bloat-regression-guard` +- HEAD before implementation: `0a1ecf99` +- Dirty state before implementation: untracked `specs/375-ui-bloat-regression-guard/`. +- Runtime UI refactor assertion: no runtime UI refactor planned. + +## Planned Commands + +- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards/UiBloatRegressionGuardTest.php` +- `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=TestLaneManifest` +- `cd apps/platform && ./vendor/bin/sail pint --dirty` +- `git diff --check` + +## Results + +| Command | Result | Notes | +|---|---|---| +| `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards/UiBloatRegressionGuardTest.php` | pass | 10 tests, 40 assertions | +| `cd apps/platform && ./vendor/bin/sail artisan test --compact --filter=TestLaneManifest` | pass | 6 tests, 321 assertions | +| `cd apps/platform && ./vendor/bin/sail php -r '... UiBloatScanner::scanConfiguredPaths("/var/www/repo", "warn") ...'` | pass | 417 files scanned, 0 blocking failures, 24 warnings, 346 manual-review findings | +| `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` | pass | `{"result":"pass"}` | +| `git diff --check` | pass | No whitespace errors | + +## Guard Result + +- Entrypoint: `apps/platform/tests/Feature/Guards/UiBloatRegressionGuardTest.php` +- Helper: `apps/platform/tests/Support/UiBloat/UiBloatScanner.php` +- Strictness default: `warn` +- Hard-fail behavior: unallowlisted customer/auditor default-surface raw ID/internal-term leakage only +- Initial scan blockers: 0 +- Allowlist file: none committed in v1 + +## Known Limitations + +- Source scanning cannot prove rendered DOM visibility. +- Manual-review findings are intentionally non-blocking in v1. +- Browser smoke is not applicable because no reachable product UI changed. + +## Final State + +- Dirty state after implementation: Spec 375 artifacts plus test/test-support changes only. +- Runtime UI files changed: no. +- Product routes, models, migrations, policies, jobs, queues, scheduler, storage, Graph contracts, Filament pages/resources, and panel providers changed: no. +- Recommended next spec: defer browser-scorecard integration and CI strictness expansion until guard counts stabilize. + +## Deployment Impact + +- Env vars: none. +- Migrations: none. +- Queues/scheduler: none. +- Storage/volumes: none. +- Filament assets: none; `filament:assets` is not required. diff --git a/specs/375-ui-bloat-regression-guard/checklists/requirements.md b/specs/375-ui-bloat-regression-guard/checklists/requirements.md new file mode 100644 index 00000000..7543e010 --- /dev/null +++ b/specs/375-ui-bloat-regression-guard/checklists/requirements.md @@ -0,0 +1,45 @@ +# Requirements Checklist: Spec 375 - UI Bloat Regression Guard v1 + +**Purpose**: Validate preparation readiness for Spec 375 before implementation. +**Created**: 2026-06-13 +**Feature**: `specs/375-ui-bloat-regression-guard/spec.md` + +## Spec Quality + +- [x] CHK001 The selected candidate is directly provided by the user as Spec 375 and aligned with the recent Spec 370-374 UI productization sequence. +- [x] CHK002 The completed-spec guardrail treats Specs 370-374 as completed context only and does not reopen or rewrite their history. +- [x] CHK003 The spec states the concrete trust/workflow problem: future UI changes can regress into bloat, unsafe customer/auditor copy, or unclear diagnostic entrypoints without early guard feedback. +- [x] CHK004 The spec defines the smallest enterprise-capable slice: one static guard/scanner entrypoint with warn-first behavior, allowlist policy, and initial report. +- [x] CHK005 Functional requirements are testable and avoid requiring a broad page refactor. +- [x] CHK006 Out-of-scope boundaries exclude runtime UI refactors, migrations, models, routes, Filament page/resource changes, browser screenshot infra, and visual regression. +- [x] CHK007 Risks, assumptions, and non-blocking open questions are recorded. + +## Constitution And Guardrails + +- [x] CHK008 UI Surface Impact is completed as `No UI surface impact` with rationale. +- [x] CHK009 Cross-cutting shared pattern reuse names existing guard/test/script conventions and avoids a runtime UI framework. +- [x] CHK010 OperationRun UX impact states no OperationRun behavior is touched. +- [x] CHK011 Provider boundary treatment keeps provider terms as scanner leakage indicators, not platform-core truth. +- [x] CHK012 Proportionality review justifies the narrow guard and rejects browser visual regression and manual-only review. +- [x] CHK013 RBAC, workspace/tenant isolation, auditability, and data minimization are addressed as no-runtime-impact constraints. +- [x] CHK014 Test governance names `surface-guard` / heavy-governance ownership and forbids hidden browser/DB fixture cost. +- [x] CHK015 Filament v5 / Livewire v4 compliance, provider registration, global search, destructive action, asset, and testing posture are stated in the plan. + +## Task Readiness + +- [x] CHK016 `tasks.md` includes repo-truth and source-input tasks before tooling edits. +- [x] CHK017 `tasks.md` includes spec-local artifact tasks before implementation. +- [x] CHK018 `tasks.md` includes tests before scanner implementation. +- [x] CHK019 `tasks.md` includes initial scan, allowlist, validation, and close-out artifact tasks. +- [x] CHK020 `tasks.md` includes explicit non-goals to prevent runtime UI refactor creep. + +## Preparation Outcome + +- [x] CHK021 Candidate Selection Gate result: pass. +- [x] CHK022 Spec Readiness Gate result: pass for preparation. +- [x] CHK023 Review outcome class: acceptable-special-case. +- [x] CHK024 Workflow outcome: keep. + +## Notes + +This checklist validates preparation only. It does not claim scanner implementation, initial scan execution, test execution, CI integration, or runtime UI changes. diff --git a/specs/375-ui-bloat-regression-guard/plan.md b/specs/375-ui-bloat-regression-guard/plan.md new file mode 100644 index 00000000..5ad18111 --- /dev/null +++ b/specs/375-ui-bloat-regression-guard/plan.md @@ -0,0 +1,258 @@ +# Implementation Plan: UI Bloat Regression Guard v1 + +**Branch**: `375-ui-bloat-regression-guard` | **Date**: 2026-06-13 | **Spec**: `specs/375-ui-bloat-regression-guard/spec.md` +**Input**: Feature specification from `specs/375-ui-bloat-regression-guard/spec.md` + +## Summary + +Build a narrow repository guard that scans TenantPilot UI source paths for known UI bloat and customer/auditor safety regression patterns from Specs 368 and 370-374. The implementation should prefer existing Pest guard conventions and surface-guard lane ownership, create spec-local rule/design/allowlist/scan artifacts, run an initial scan in `warn` mode, and avoid any runtime UI page refactor. + +## Technical Context + +**Language/Version**: PHP 8.4.15, Laravel 12.52, Filament 5.2.1, Livewire 4.1.4 +**Primary Dependencies**: Pest 4.3, PHPUnit 12, existing Laravel/Filament app and test harness +**Storage**: N/A - no database or product persistence; spec-local Markdown reports only +**Testing**: Pest guard/source-scan tests preferred; optional Artisan command test only if a command is selected +**Validation Lanes**: heavy-governance / surface-guard by default; targeted command/test for local validation +**Target Platform**: local Laravel app and Gitea-compatible CI runners +**Project Type**: Laravel monolith under `apps/platform` plus repo-level scripts/docs +**Performance Goals**: deterministic local scan; no browser, DB, provider, queue, or network dependency +**Constraints**: no runtime UI refactor; no fast-feedback lane pollution unless scan cost is proven narrow; hard-fail only clear customer/auditor default-surface leakage in v1 +**Scale/Scope**: configured scan over selected UI source directories, not full repository or generated assets + +## UI / Surface Guardrail Plan + +- **Guardrail scope**: workflow-only guardrail change. +- **Affected routes/pages/actions/states/navigation/panel/provider surfaces**: none. +- **No-impact class, if applicable**: tooling-only / test-only / documentation-artifact change. +- **Native vs custom classification summary**: N/A - no rendered UI. +- **Shared-family relevance**: status messaging, header actions, dashboard/stat cards, evidence/report viewers, customer/auditor defaults, diagnostic entrypoints. +- **State layers in scope**: none for runtime; source-scan surface classification only. +- **Audience modes in scope**: customer/read-only, operator-MSP, support-platform as scanner classifications. +- **Decision/diagnostic/raw hierarchy plan**: derive guard rules from Spec 370-374 hierarchy: decision/customer-safe content first, diagnostics second, support/raw third. +- **Raw/support gating plan**: guard reports raw/support terms in customer/auditor defaults as blocking or manual review depending on context/allowlist. +- **One-primary-action / duplicate-truth control**: scanner flags missing primary question/next action, header action overload, repeated status/lifecycle/timestamp/count noise, and zero-card spam. +- **Handling modes by drift class or surface**: clear customer/auditor leakage is hard-stop candidate; diagnostic/operator ambiguity is review-mandatory; allowlisted legacy debt is report-only. +- **Repository-signal treatment**: report/warn by default, future hard-stop candidate for additional rules after allowlist cleanup. +- **Special surface test profiles**: surface-guard; no browser smoke. +- **Required tests or manual smoke**: targeted source-scan fixture/sample coverage and initial scan report. +- **Exception path and spread control**: allowlist entries must be explicit, reasoned, reviewed, and scoped by rule/file/pattern. +- **Active feature PR close-out entry**: Guardrail. +- **UI/Productization coverage decision**: No UI surface impact. +- **Coverage artifacts to update**: none. +- **No-impact rationale**: no reachable UI is added, removed, renamed, or materially changed. +- **Navigation / Filament provider-panel handling**: no panel/provider changes. +- **Screenshot or page-report need**: no; guard-only spec. + +## Shared Pattern & System Fit + +- **Cross-cutting feature marker**: yes, because the guard reviews cross-surface UI interaction classes; no runtime shared interaction family changes. +- **Systems touched**: likely `apps/platform/tests/Feature/Guards`, `apps/platform/tests/Architecture`, `apps/platform/tests/Pest.php`, `apps/platform/tests/Support/TestLaneManifest.php` when heavy-governance ownership is selected, optional `apps/platform/app/Console/Commands`, optional repo scripts, and spec-local artifacts. +- **Shared abstractions reused**: existing source-scan guard patterns, Pest group/lane conventions, and `scripts/check-ui-productization-coverage` as a reference for report/fail behavior. +- **New abstraction introduced? why?**: at most one narrow scanner helper or guard test. It is justified only if inline test code would be too duplicated or unreadable. +- **Why the existing abstraction was sufficient or insufficient**: current UI coverage guard checks acknowledgement of UI impact, not source content bloat or customer-safety leakage. +- **Bounded deviation / spread control**: no product runtime abstraction; no framework under `app/Support` unless implementation proves test-local code is insufficient. + +## OperationRun UX Impact + +- **Touches OperationRun start/completion/link UX?**: no. +- **Central contract reused**: N/A. +- **Delegated UX behaviors**: N/A. +- **Surface-owned behavior kept local**: none. +- **Queued DB-notification policy**: N/A. +- **Terminal notification path**: N/A. +- **Exception path**: none. + +## Provider Boundary & Portability Fit + +- **Shared provider/platform boundary touched?**: no runtime provider seam. +- **Provider-owned seams**: N/A. +- **Platform-core seams**: N/A. +- **Neutral platform terms / contracts preserved**: guard language must describe provider/internal term leakage rather than making provider-specific labels platform truth. +- **Retained provider-specific semantics and why**: provider terms can remain legitimate in provider-owned diagnostic/operator surfaces and should be manual-review/allowlisted there. +- **Bounded extraction or follow-up path**: none. + +## Constitution Check + +*GATE: Must pass before implementation. Re-check after guard design is finalized.* + +- Inventory-first: N/A - no inventory/snapshot behavior. +- Read/write separation: passes - no product write/change function; guard only reads source files and writes spec-local reports. +- Graph contract path: passes - no Graph calls. +- Deterministic capabilities: N/A - no capability resolver changes. +- RBAC-UX: passes - no authorization behavior changes; future implementation must not treat UI visibility as security. +- Workspace isolation: passes - no runtime workspace/tenant queries. +- Tenant isolation: passes - no tenant-owned data reads. +- Run observability: passes - no long-running remote/queued work and no OperationRun semantics. +- OperationRun start UX: N/A. +- Data minimization: guard reports must not include secrets, credentials, provider raw responses, or sensitive customer data. +- Test governance: surface-guard/heavy-governance classification is explicit; no hidden browser/DB fixtures. +- Proportionality: one narrow guard is justified by current UI safety/productization risk. +- No premature abstraction: prefer a single test/scanner path; avoid reusable framework unless needed for readability and tests. +- Persisted truth: no product persistence; spec-local reports only. +- Behavioral state: rule IDs are tooling diagnostics, not product state. +- UI semantics: guard consumes Spec 370-374 UI semantics and must not create a new UI interpretation framework. +- Shared pattern first: reuse existing guard/test/script conventions. +- Provider boundary: provider terms are scanner indicators only. +- V1 explicitness / few layers: direct text scanning and simple heuristics preferred over AST-heavy or browser-required tooling. +- Spec discipline / bloat check: proportionality review is complete. +- Filament-native UI: no rendered Filament UI changes; if implementation touches Filament source only by scanning it, no asset or provider updates are needed. +- UI/Productization coverage: no-impact decision is checked and rationalized. + +## Test Governance Check + +- **Test purpose / classification by changed surface**: Heavy-Governance / surface-guard for broad source scanning; unit-like sample cases may live inside the guard test. +- **Affected validation lanes**: heavy-governance or explicit targeted guard test/command; no browser lane. +- **Why this lane mix is the narrowest sufficient proof**: The feature protects cross-surface source patterns and does not render UI. +- **Narrowest proving command(s)**: + - `cd apps/platform && php vendor/bin/pest tests/Feature/Guards/UiBloatRegressionGuardTest.php` or exact selected guard file. + - If heavy-governance ownership is registered: targeted lane manifest/placement contract coverage proving the guard is discoverable by `test:heavy`, or a documented targeted-only validation if not registered. + - If an Artisan command is selected: `cd apps/platform && php artisan tenantpilot:check-ui-bloat --strictness=warn`. + - `git diff --check`. + - `cd apps/platform && php vendor/bin/pint --dirty` if PHP changed. +- **Fixture / helper / factory / seed / context cost risks**: none expected; use source fixture strings/files only. +- **Expensive defaults or shared helper growth introduced?**: no; any scanner helper must avoid DB/application boot beyond what Pest already needs. +- **Heavy-family additions, promotions, or visibility changes**: one new bounded surface-guard family or test. +- **Surface-class relief / special coverage rule**: no product page browser smoke. +- **Closing validation and reviewer handoff**: reviewer confirms no runtime UI refactor, no raw hard-fail expansion beyond customer/auditor defaults, no hidden fast-feedback lane cost, and report artifacts are complete. +- **Budget / baseline / trend follow-up**: record guard runtime and lane impact in validation report. +- **Review-stop questions**: lane fit, breadth, false positives, allowlist quality, hidden cost, and scope creep. +- **Escalation path**: document-in-feature; follow-up-spec only if CI hardening or browser integration becomes structural. +- **Active feature PR close-out entry**: Guardrail. +- **Why no dedicated follow-up spec is needed**: v1 guard setup is bounded; follow-up specs are limited to browser fixture coverage, scorecard integration, and CI strictness expansion. + +## Project Structure + +### Documentation (this feature) + +```text +specs/375-ui-bloat-regression-guard/ +|-- spec.md +|-- plan.md +|-- tasks.md +|-- checklists/ +| `-- requirements.md +`-- artifacts/ + |-- source-summary.md + |-- guard-rules.md + |-- scanner-design.md + |-- allowlist-policy.md + |-- initial-scan-report.md + |-- affected-files.md + |-- validation-report.md + `-- follow-up-recommendations.md +``` + +### Likely Source/Test Surfaces + +```text +apps/platform/tests/Feature/Guards/ +apps/platform/tests/Architecture/ +apps/platform/tests/Pest.php +apps/platform/tests/Support/TestLaneManifest.php # if heavy-governance lane ownership is selected +apps/platform/app/Console/Commands/ # only if command is narrower than Pest-only guard +scripts/ # only if repo-level script is chosen +``` + +### Initial Scan Scope + +```text +apps/platform/app/Filament +apps/platform/resources/views/filament +apps/platform/app/Support/EnvironmentDashboard +apps/platform/app/Support/Navigation +apps/platform/app/Support/OpsUx +apps/platform/app/Support/SupportDiagnostics +apps/platform/app/Support/Ui +apps/platform/app/Support/Workspaces +``` + +Candidate paths `apps/platform/resources/views/components` and `apps/platform/app/View` are not present in current repo truth and must be recorded as `not available` if still absent during implementation. Additional `apps/platform/app/Support` subpaths may be added only when `scanner-design.md` documents why they are UI-support paths; `apps/platform/app/Support` must not be scanned wholesale in v1. + +### Exclusions / Separate Classification + +```text +vendor +node_modules +storage +bootstrap/cache +public/build +database dumps +spec artifacts +screenshots +generated reports +tests unless explicitly used as scanner fixtures +translation dictionaries unless implementation proves rendered default UI copy is being checked +``` + +**Structure Decision**: Use a single repo-conform guard entrypoint. Prefer `apps/platform/tests/Feature/Guards/UiBloatRegressionGuardTest.php` because existing broad UI/source guard tests live under `tests/Feature/Guards`. If the guard is intended to run in `test:heavy`, it must be grouped in `apps/platform/tests/Pest.php` and registered as a `surface-guard` family/hotspot in `apps/platform/tests/Support/TestLaneManifest.php`; otherwise the source summary and validation report must document targeted-only ownership. + +## Complexity Tracking + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|-------------------------------------| +| New source scanner/guard | Existing UI coverage guard does not detect specific bloat/customer-safety copy patterns | Manual checklist alone does not reliably catch cross-surface drift | +| Rule IDs and allowlist policy | Findings need stable review language and controlled exceptions | Unstructured grep output would be noisy and unactionable | + +## Proportionality Review + +- **Current operator problem**: Future UI work can reintroduce unsafe customer/auditor copy and noisy, unprioritized surfaces without early review feedback. +- **Existing structure is insufficient because**: `scripts/check-ui-productization-coverage` proves coverage acknowledgement, not the content quality of changed source. +- **Narrowest correct implementation**: One source scanner/test/command, ten bounded rule groups, warn default, hard-fail only clear customer/auditor leakage, and spec-local artifacts. +- **Ownership cost created**: Rule maintenance, initial scan debt triage, allowlist review, and one guard lane/test. +- **Alternative intentionally rejected**: Full browser visual regression and broad screenshot diffs are too expensive and unnecessary for v1; page refactors are explicitly not this spec. +- **Release truth**: Current-release truth protecting completed UI productization. + +## Implementation Phases + +### Phase 1 - Repo Truth And Source Inputs + +Verify existing guard/test/script conventions, existing console command patterns, Spec 368 inputs, and Spec 370-374 artifacts. Record available and missing inputs in `artifacts/source-summary.md`. + +### Phase 2 - Guard Rules And Design Artifacts + +Create `guard-rules.md`, `scanner-design.md`, and `allowlist-policy.md` before runtime/tooling changes so review can stop scope growth early. + +### Phase 3 - Tests First / Fixture Cases + +Add targeted sample cases proving customer/auditor hard failures, manual-review heuristics, allowlist shape, and strictness behavior. + +### Phase 4 - Scanner / Entrypoint Implementation + +Implement one repo-conform entrypoint. Prefer Pest guard unless final discovery proves Artisan command or script is narrower. + +### Phase 5 - Initial Scan And Report + +Run the guard in `warn` mode, generate `initial-scan-report.md`, and classify findings without broad UI fixes. + +### Phase 6 - Validation And Close-Out Artifacts + +Complete affected files, validation report, follow-up recommendations, and run targeted validation commands. + +## Deployment / Ops Considerations + +- **Environment variables**: none. +- **Migrations**: none. +- **Queues/scheduler**: none. +- **Storage/volumes**: none. +- **Filament assets**: no registered assets expected; `filament:assets` is not required unless implementation unexpectedly registers assets, which should be treated as scope drift. +- **CI**: v1 may run as a targeted guard or report-only heavy-governance check. Do not make broad heuristic failures CI-blocking before allowlist cleanup. + +## Filament v5 Output Contract + +- **Livewire v4.0+ compliance**: The app has Livewire 4.1.4; this spec must not introduce Livewire v3 references. +- **Provider registration location**: No panel providers are changed. If an implementation unexpectedly touches panel providers, Laravel 12 registration remains `apps/platform/bootstrap/providers.php`. +- **Global search**: No resources or global search behavior are changed. +- **Destructive actions**: No destructive/high-impact actions are added or changed. Any existing destructive actions observed by scanner remain runtime-owned and must retain confirmation, authorization, and audit behavior. +- **Asset strategy**: No new Filament assets. No deploy `filament:assets` impact expected. +- **Testing plan**: targeted Pest/source-scan guard or selected command test; no browser smoke required. + +## Risk Controls + +- Keep scanner deterministic and text-based. +- Default ambiguous findings to manual review. +- Keep hard failures narrow. +- Separate existing debt from new blocking failures. +- Do not auto-fix files. +- Document every allowlist entry. +- Record any chosen implementation option and rejected alternatives. diff --git a/specs/375-ui-bloat-regression-guard/spec.md b/specs/375-ui-bloat-regression-guard/spec.md new file mode 100644 index 00000000..58970dfd --- /dev/null +++ b/specs/375-ui-bloat-regression-guard/spec.md @@ -0,0 +1,359 @@ +# Feature Specification: UI Bloat Regression Guard v1 + +**Feature Branch**: `375-ui-bloat-regression-guard` +**Created**: 2026-06-13 +**Status**: Draft +**Input**: User-provided Spec 375 draft, Specs 368 and 370-374 UI/productization artifacts, and current repo guard/test conventions. + +## Spec Candidate Check *(mandatory - SPEC-GATE-001)* + +- **Problem**: Specs 370-374 improved TenantPilot UI information architecture, customer/auditor safety, and diagnostic guidance, but future feature work can regress into repeated status panels, zero-card spam, raw IDs, provider/debug language, missing primary next actions, and unclear diagnostic entrypoints. +- **Today's failure**: New or changed Filament pages can reintroduce "select * to UI" behavior without a focused guard noticing the drift before review. The highest-risk failure is a customer/auditor default surface exposing raw IDs or internal/debug/provider terms. +- **User-visible improvement**: Reviewers and implementers get an actionable guard report that separates blocking customer-safety failures from warnings and manual-review findings before UI bloat reaches users. +- **Smallest enterprise-capable version**: A deterministic static scanner or Pest guard that scans configured UI source paths, classifies likely surface audience, applies ten bounded rule groups, supports `report`, `warn`, and `fail` behavior, documents an allowlist policy, and writes an initial scan report. +- **Explicit non-goals**: No runtime UI refactor, no visual regression system, no screenshot diff infrastructure, no accessibility/performance audit, no new product feature, no new navigation, no migrations, no persisted product data, no page redesign, and no broad browser fixture work. +- **Permanent complexity imported**: One narrow guard/test/tooling entrypoint, stable rule IDs, a documented allowlist policy, and spec-local scan/validation artifacts. No new model, table, enum/status family, provider framework, UI component system, or product runtime source of truth. +- **Why now**: Spec 374 completed the diagnostic entrypoint consolidation after Specs 370-373. Guardrails should be installed immediately after the productization pass so future agents do not unknowingly undo the customer/auditor and diagnostic-surface safety rules. +- **Why not local**: A page-local checklist does not catch cross-surface regression patterns. The repo already has guard-oriented tests and UI/productization coverage scripts, so a narrow guard is the smallest reliable enforcement surface. +- **Approval class**: Core Enterprise. +- **Red flags triggered**: New meta-infrastructure and multiple surfaces. Defense: the guard is non-runtime, uses existing source-scan/test conventions, creates no product truth, defaults ambiguous findings to manual review, and hard-fails only clear customer/auditor safety violations. +- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 1 | Wiederverwendung: 2 | **Gesamt: 10/12** +- **Decision**: approve as a narrow guardrail slice. + +## Candidate Selection And Completed-Spec Guardrail + +- **Selected candidate**: UI Bloat Regression Guard v1. +- **Source**: User-provided Spec 375 draft attached to the request. +- **Roadmap relationship**: Supports the current UI/product maturity lane and protects the Spec 370-374 surface productization sequence. +- **Related completed specs**: + - Spec 370 is a completed docs-only global surface IA contract with checklist and validation artifacts. + - Spec 371 is a completed operator-surface productization package with implementation/browser artifacts. + - Spec 372 is a completed customer/auditor safety package with customer-surface contracts, checklist, and browser artifacts. + - Spec 373 is a completed diagnostic-surface separation package with diagnostic artifacts and validation. + - Spec 374 is completed/repo-real context for diagnostic entrypoint and support diagnostics consolidation. +- **Guardrail result**: Specs 370-374 are inputs only. This spec must not rewrite, normalize, reopen, or remove their implementation history, validation results, completed task markers, browser evidence, or close-out language. +- **Close alternatives deferred**: + - Browser Audit Fixture Coverage for Evidence/System Surfaces v1. + - Browser scorecard integration for guard output. + - Final post-productization browser closeout audit. + - CI hard-fail expansion after allowlist cleanup. + +## Spec Scope Fields *(mandatory)* + +- **Scope**: canonical-view / repository guardrail. +- **Primary Routes**: N/A - no reachable product routes are added, removed, renamed, or changed. +- **Data Ownership**: No database tables or tenant/workspace-owned records are changed. Spec-local scan reports are documentation artifacts only. +- **RBAC**: No authorization behavior changes. Guard scanning must not infer access rights or weaken existing Gates/Policies. + +For canonical-view specs: + +- **Default filter behavior when tenant-context is active**: N/A - no runtime query or tenant filter behavior changes. +- **Explicit entitlement checks preventing cross-tenant leakage**: N/A for runtime behavior. The guard must ignore routes/models/migrations as runtime UI findings unless they are rendered default-visible UI copy. + +## UI Surface Impact *(mandatory - UI-COV-001)* + +Does this spec add, remove, rename, or materially change any reachable UI surface? + +- [x] No UI surface impact +- [ ] 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 +- [ ] Customer-facing surface changed +- [ ] Dangerous action changed +- [ ] Status/evidence/review presentation changed +- [ ] Workspace/environment context presentation changed + +## UI/Productization Coverage *(mandatory when UI Surface Impact is not "No UI surface impact"; otherwise write `N/A - no reachable UI surface impact` plus rationale)* + +- **Route/page/surface**: N/A - repository guard/test/tooling only. +- **Current or new page archetype**: N/A. +- **Design depth**: N/A. +- **Repo-truth level**: repo-verified guardrail need, no page change. +- **Existing pattern reused**: Existing Pest guard/source-scan conventions under `apps/platform/tests/Feature/Guards`, `apps/platform/tests/Architecture`, and `scripts/check-ui-productization-coverage`. +- **New pattern required**: none for product UI. +- **Screenshot required**: no. This is a static guard spec, not a UI productization pass. +- **Page audit required**: no. +- **Customer-safe review required**: yes for guard rules, no for changed UI because no UI changes are in scope. +- **Dangerous-action review required**: no changed dangerous action. +- **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 reachable UI surface impact` +- **No-impact rationale when applicable**: The later implementation may add tests, a guard command, config, script, and spec-local reports, but it must not change rendered product pages, navigation, Filament panels, modals, tables, forms, actions, or customer/auditor surfaces. + +## Cross-Cutting / Shared Pattern Reuse *(mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write `N/A - no shared interaction family touched`)* + +- **Cross-cutting feature?**: yes, as a guardrail over cross-surface UI classes; no runtime shared interaction family is modified. +- **Interaction class(es)**: status messaging, header actions, dashboard/stat cards, evidence/report viewers, customer/auditor default surfaces, diagnostic entrypoints. +- **Systems touched**: Existing source files are scanned; no runtime shared UI system should be changed by default. +- **Existing pattern(s) to extend**: Existing source-scan/Pest guard patterns and `scripts/check-ui-productization-coverage`. +- **Shared contract / presenter / builder / renderer to reuse**: None in runtime. Reuse Spec 370 contract artifacts and existing guard test conventions instead of adding a product UI framework. +- **Why the existing shared path is sufficient or insufficient**: Existing UI coverage guard protects changed UI files from missing coverage decisions, but it does not inspect source content for repeated status noise, raw ID leakage, customer/internal copy, or diagnostic guidance regression. +- **Allowed deviation and why**: A focused source-scanning guard is allowed because the existing coverage guard answers "was coverage acknowledged?" not "did known bloat patterns reappear?" +- **Consistency impact**: Findings must use stable rule IDs and preserve Spec 370-374 language without creating a second product taxonomy. +- **Review focus**: Verify the implementation stays a narrow guard, does not auto-fix pages, and does not hard-fail ambiguous heuristic matches in v1. + +## OperationRun UX Impact *(mandatory when the feature creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`; otherwise write `N/A - no OperationRun start or link semantics touched`)* + +- **Touches OperationRun start/completion/link UX?**: no. +- **Shared OperationRun UX contract/layer reused**: N/A. +- **Delegated start/completion UX behaviors**: N/A. +- **Local surface-owned behavior that remains**: none. +- **Queued DB-notification policy**: N/A. +- **Terminal notification path**: N/A. +- **Exception required?**: none. + +## Provider Boundary / Platform Core Check *(mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write `N/A - no shared provider/platform boundary touched`)* + +- **Shared provider/platform boundary touched?**: no runtime seam change. +- **Boundary classification**: N/A. +- **Seams affected**: none. +- **Neutral platform terms preserved or introduced**: The guard should prefer neutral findings such as "provider/internal term in customer default surface" and must not normalize Microsoft-specific runtime semantics. +- **Provider-specific semantics retained and why**: Some provider terms remain legitimate in provider-owned operator/diagnostic surfaces; the guard must classify those as manual-review or allowlisted rather than hard-fail outside customer/auditor defaults. +- **Why this does not deepen provider coupling accidentally**: The scanner uses provider terms as leakage indicators, not as platform-core truth. +- **Follow-up path**: none for runtime. Future guard strictness changes should be documented in this spec's follow-up recommendations. + +## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)* + +| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note | +|---|---|---|---|---|---|---| +| UI bloat guard/test/tooling | no rendered surface change | N/A | status/action/evidence/diagnostic guardrail only | none | no | `N/A - repository guard only` | +| Spec-local artifacts and initial scan report | no | N/A | review workflow evidence | none | no | `N/A - documentation artifacts` | + +## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)* + +N/A - no operator-facing product surface is added or materially changed. + +## Audience-Aware Disclosure *(mandatory when operator-facing surfaces are changed)* + +N/A - no rendered detail or status surface is changed. The guard exists to protect customer/read-only default paths from raw JSON, fingerprints, IDs, internal reason ownership, platform reason families, and debug semantics. + +## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)* + +N/A - no operator-facing list, detail, queue, audit, config, report, table, or form surface is changed. + +## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)* + +N/A - no new or materially refactored operator-facing page is in scope. + +## Proportionality Review *(mandatory when structural complexity is introduced)* + +- **New source of truth?**: no product source of truth. +- **New persisted entity/table/artifact?**: no database or product artifact. Spec-local Markdown reports are required implementation evidence only. +- **New abstraction?**: yes, a narrow guard/scanner abstraction may be introduced in tests/tooling if it is the smallest repo-conform implementation. +- **New enum/state/reason family?**: no product enum or state family. Stable rule IDs are tooling diagnostics, not domain state. +- **New cross-domain UI framework/taxonomy?**: no. The guard consumes Spec 370-374 contracts and must not become a UI framework. +- **Current operator problem**: Future changes can silently reintroduce unsafe customer/auditor copy, status duplication, zero-card spam, unclear next actions, and diagnostic ambiguity. +- **Existing structure is insufficient because**: Existing coverage guards prove acknowledgement of UI surface impact, but they do not scan rendered-source copy and action structures for known bloat/leakage patterns. +- **Narrowest correct implementation**: One scanner/test/command path, configurable scanned paths, bounded rule groups, allowlist policy, and initial report. No page rewrites and no browser dependency. +- **Ownership cost**: Rule maintenance, false-positive triage, allowlist review, a targeted test/guard lane, and scan report upkeep. +- **Alternative intentionally rejected**: A full browser visual-regression framework is too broad for v1; manual-only review is too easy to miss; page-by-page refactors would violate the guard-only scope. +- **Release truth**: Current-release truth. It protects already completed sellability and safety improvements. + +### Compatibility posture + +This feature assumes a pre-production environment. No legacy aliases, migration shims, or runtime compatibility paths are needed. + +## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)* + +- **Test purpose / classification**: Heavy-Governance / surface-guard for broad source scanning, with targeted unit-like cases inside the guard test where practical. +- **Validation lane(s)**: heavy-governance or explicit targeted guard command; not fast-feedback by default unless the implementation proves the scan is narrow and cheap. +- **Why this classification and these lanes are sufficient**: Broad UI source scanning is a surface-guard concern and should not silently enter fast-feedback. +- **New or expanded test families**: One bounded Spec 375 UI bloat guard family. +- **Fixture / helper cost impact**: No database, browser, workspace, tenant, provider, session, or seed fixture should be required. +- **Heavy-family visibility / justification**: Explicit because it scans multiple UI source directories and protects cross-surface behavior. +- **Special surface test profile**: surface-guard / standard-native relief for runtime pages. +- **Standard-native relief or required special coverage**: No product page smoke required. The guard itself needs targeted source fixture/sample coverage and initial scan output. +- **Reviewer handoff**: Reviewers must verify no broad page refactor, no hidden browser/auth fixture dependency, no broad hard-fail heuristic, and no fast-feedback lane pollution unless intentionally documented. +- **Budget / baseline / trend impact**: Possible new heavy-governance cost. The implementation must document runtime and whether it changes lane budgets. +- **Escalation needed**: document-in-feature. Escalate to follow-up-spec only if scan breadth or CI strictness becomes structural. +- **Active feature PR close-out entry**: Guardrail. +- **Planned validation commands**: + - `git diff --check` + - `cd apps/platform && ./vendor/bin/pint --dirty` if PHP files change + - targeted Pest guard test or guard command selected during implementation + - guard run in `warn` mode to generate the initial scan report + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Reviewer Sees UI Bloat Findings Before Merge (Priority: P1) + +As a TenantPilot maintainer reviewing a future UI change, I want a guard report that highlights known bloat patterns so I can stop unsafe or noisy UI before it merges. + +**Why this priority**: It protects the completed UI IA/productization investment from drift. + +**Independent Test**: Run the guard against fixture/sample files and current UI paths; verify findings are grouped by rule, severity, file, pattern, reason, and suggested action. + +**Acceptance Scenarios**: + +1. **Given** a UI source file with repeated status/lifecycle text, **When** the guard runs in `warn` mode, **Then** it reports a `UIBLOAT_REPEATED_STATUS` manual-review finding without failing the run. +2. **Given** a source file with multiple zero metric labels, **When** the guard runs, **Then** it reports `UIBLOAT_ZERO_METRIC_CARD` as warning or manual-review rather than treating every zero as blocking. + +--- + +### User Story 2 - Customer/Auditor Default Surfaces Stay Safe (Priority: P1) + +As a customer/auditor surface reviewer, I want clear hard-fail rules for raw IDs and internal/debug/provider terms in default-visible customer/auditor surfaces. + +**Why this priority**: This is the highest trust risk identified by Specs 368, 370, and 372. + +**Independent Test**: Run fixture/sample cases for likely customer/auditor files containing raw ID labels or blocked internal terms. + +**Acceptance Scenarios**: + +1. **Given** a likely customer/auditor view file includes `operation context` in default-visible copy, **When** the guard runs in `warn` or `fail` mode, **Then** the finding is blocking unless explicitly allowlisted. +2. **Given** a customer/auditor view file contains a raw ID label inside a documented collapsed technical-details block, **When** the guard runs, **Then** it reports manual-review or allowlisted status instead of a blanket fail. + +--- + +### User Story 3 - Diagnostic Guidance And Entrypoint Drift Is Visible (Priority: P2) + +As an operator/support reviewer, I want diagnostic pages and actions flagged when they lose guidance or blur Support Diagnostics versus Repair Diagnostics semantics. + +**Why this priority**: Spec 373 and Spec 374 deliberately separated guidance-first diagnostics and entrypoint semantics. + +**Independent Test**: Run fixture/sample cases for diagnostic files with technical terms but no guidance markers, and for ambiguous "Diagnostics Hub" or broad health labels pointing at repair-only routes. + +**Acceptance Scenarios**: + +1. **Given** a diagnostic file has many technical details and no "recommended first check" or similar guidance marker, **When** the guard runs, **Then** it reports `UIBLOAT_DIAGNOSTIC_GUIDANCE_MISSING` as manual-review. +2. **Given** copy uses broad "All diagnostics" language for a repair-only route, **When** the guard runs, **Then** it reports `UIBLOAT_DIAGNOSTIC_ENTRYPOINT_AMBIGUOUS` as manual-review unless the case is clearly customer-facing and misleading. + +--- + +### User Story 4 - Allowlist And Initial Scan Are Reviewable (Priority: P2) + +As a maintainer, I want existing debt, false positives, and intentional exceptions documented so the guard does not become ignored noise. + +**Why this priority**: A guard that cannot distinguish known debt from blocking regressions will be bypassed. + +**Independent Test**: Review `allowlist-policy.md` and `initial-scan-report.md`; verify every allowlist entry shape requires rule ID, file, pattern, reason, surface type, audience, review marker, and owner/spec. + +**Acceptance Scenarios**: + +1. **Given** a finding is known existing debt, **When** the initial scan report is generated, **Then** it is recorded separately from new blocking failures. +2. **Given** an allowlist entry lacks a reason or review marker, **When** the guard validates allowlist shape, **Then** it fails or reports a clear guard configuration error. + +## Functional Requirements + +- **FR-375-001**: The implementation MUST create a repo-conform UI bloat scanner as exactly one primary entrypoint: a Pest guard, an Artisan command, or a script. Prefer existing Pest guard conventions unless repo discovery proves a command/script is narrower. +- **FR-375-002**: The scanner MUST discover files from repo-verified configured UI source paths, initially including `apps/platform/app/Filament` and `apps/platform/resources/views/filament`. Optional candidates such as `apps/platform/resources/views/components` and `apps/platform/app/View` MUST be recorded as `not available` when absent, and `apps/platform/app/Support` MUST be limited to explicit UI-support subpaths selected in `scanner-design.md` rather than scanned wholesale. +- **FR-375-003**: The scanner MUST exclude or separately classify vendor, node modules, storage, build artifacts, generated reports, screenshots, database dumps, specs, tests, and translations unless they are intentionally scanned as non-runtime findings. +- **FR-375-004**: The scanner MUST classify likely surfaces as customer/auditor, diagnostic/support, operator, or unknown using path and content heuristics. +- **FR-375-005**: The scanner MUST implement stable rule IDs for the ten v1 groups: zero metric/card spam, repeated status/lifecycle noise, customer raw IDs, customer internal/debug/provider terms, missing primary question/next action, header action overload, evidence/diagnostics mixing, technical metadata in main content, diagnostic guidance missing, and diagnostic entrypoint ambiguity. +- **FR-375-006**: The scanner MUST support `report`, `warn`, and `fail` strictness semantics or, if implementation evidence proves full strictness too large for v1, at minimum default to `warn`, distinguish blocking from non-blocking findings, and return non-zero only for hard customer/auditor safety failures. +- **FR-375-007**: The scanner MUST hard-fail clear customer/auditor default-surface raw ID labels and blocked internal/debug/provider terms unless explicitly allowlisted with reason. +- **FR-375-008**: Ambiguous heuristic matches MUST default to `manual-review-required` rather than blocking in v1. +- **FR-375-009**: The implementation MUST create `artifacts/guard-rules.md`, `artifacts/scanner-design.md`, `artifacts/allowlist-policy.md`, `artifacts/source-summary.md`, `artifacts/initial-scan-report.md`, `artifacts/affected-files.md`, `artifacts/validation-report.md`, and `artifacts/follow-up-recommendations.md`. +- **FR-375-010**: The source summary MUST classify required Spec 368 and Spec 370-374 inputs as available, not available, repo-verified, browser-verified, derived, or deferred, without claiming missing artifacts as verified. +- **FR-375-011**: The initial scan report MUST separate blocking failures, warnings, manual-review findings, allowlisted findings, false positives, known existing debt, and recommended follow-ups. +- **FR-375-012**: The allowlist policy and scanner design MUST name the concrete allowlist storage path and format, or explicitly record that v1 uses no committed allowlist file. If an allowlist file exists, it MUST forbid blanket allowlists for entire UI directories or all customer surfaces and require a rule ID, file, pattern, reason, surface type, audience, review/expiry marker, and owner/spec. +- **FR-375-013**: The guard MUST not auto-fix UI files, mutate runtime pages, or perform broad page refactors. +- **FR-375-014**: The implementation MUST document the selected implementation option and why the rejected options were broader or less repo-conform. +- **FR-375-015**: The implementation MUST record whether the guard belongs to heavy-governance/surface-guard lane ownership and whether any CI integration is report-only, warn-only, or deferred. + +## Non-Functional Requirements + +- **NFR-375-001**: The guard must be deterministic and runnable without browser auth, database state, Graph/provider calls, queues, or external network access. +- **NFR-375-002**: Findings must be actionable: rule ID, file, matched pattern, surface classification, severity/result, reason, and suggested action. +- **NFR-375-003**: False-positive control is part of v1. Hard failures are limited to clear customer/auditor default-surface leakage. +- **NFR-375-004**: The guard must not expand fast-feedback lane cost silently. +- **NFR-375-005**: Reports should be human-readable. Machine-readable JSON is optional and may be deferred. + +## Rule Groups + +The v1 guard must cover: + +1. `UIBLOAT_ZERO_METRIC_CARD` +2. `UIBLOAT_REPEATED_STATUS` +3. `UIBLOAT_CUSTOMER_RAW_ID` +4. `UIBLOAT_CUSTOMER_INTERNAL_TERM` +5. `UIBLOAT_MISSING_PRIMARY_QUESTION` +6. `UIBLOAT_HEADER_ACTION_OVERLOAD` +7. `UIBLOAT_EVIDENCE_DIAGNOSTICS_MIXED` +8. `UIBLOAT_TECH_METADATA_MAIN` +9. `UIBLOAT_DIAGNOSTIC_GUIDANCE_MISSING` +10. `UIBLOAT_DIAGNOSTIC_ENTRYPOINT_AMBIGUOUS` + +Optional future rule IDs such as scope/context ambiguity, provider-term business UI, card flood, duplicated timestamp, and customer action overload are follow-up candidates only unless implementation proves they are trivial configuration within the same v1 guard. + +## Data / Truth Source Requirements + +- The guard derives findings from repository source text and configured rule patterns. +- The guard report is evidence for review, not product truth. +- No database persistence, new model, new migration, or customer-visible data store is allowed. +- Spec 368 and Specs 370-374 artifacts are source inputs; completed implementation evidence remains historical context. + +## RBAC / Security Requirements + +- No runtime authorization behavior changes. +- No UI visibility changes. +- No secrets, tokens, credential payloads, provider raw responses, or customer data should be written into scan reports. +- The guard must avoid reading runtime environment secrets. + +## Auditability / Observability Requirements + +- No `AuditLog` entries or `OperationRun` records are created by the guard. +- The validation report must record branch, HEAD, dirty state before/after, commands run, selected guard entrypoint, scan result, and known limitations. + +## Out Of Scope + +- Runtime UI page refactors. +- Customer Review Workspace, Environment Review, Review Pack, Stored Report, Evidence Snapshot, OperationRun, Backup Set, Restore Run, Operations Hub, Environment Dashboard, Baseline Profile, Provider Connections, Environment Diagnostics, Required Permissions, System Panel, or diagnostic entrypoint redesign. +- New migrations, models, persisted product artifacts, jobs, policies, routes, Livewire components, Filament pages/resources, navigation entries, or Graph calls. +- Browser screenshot capture or visual regression testing. +- Full accessibility, performance, or visual QA audit. +- CI hard-fail expansion beyond clear customer/auditor safety failures before allowlist cleanup. + +## Acceptance Criteria + +- **AC-375-001**: `guard-rules.md` documents all ten rule groups, stable rule IDs, patterns, applicability, default result, strictness, allowlist behavior, and examples. +- **AC-375-002**: A repo-conform scanner/test/command exists and has a documented entrypoint. +- **AC-375-003**: Strictness semantics exist with `warn` as the v1 default and non-zero exit only for hard customer/auditor safety failures unless `fail` is intentionally selected. +- **AC-375-004**: Clear customer/auditor raw ID or internal/debug/provider default-surface findings are blocking unless allowlisted with reason. +- **AC-375-005**: `initial-scan-report.md` exists and separates blocking, warning, manual-review, allowlisted, false-positive, known-debt, and follow-up buckets. +- **AC-375-006**: `allowlist-policy.md` exists, forbids blanket allowlists, and records the concrete allowlist file path/format or the explicit v1 no-allowlist-file decision. +- **AC-375-007**: No runtime UI page refactor is performed. +- **AC-375-008**: Test/spec/generated/route/model/migration findings are ignored or separated from runtime UI findings. +- **AC-375-009**: Diagnostic guidance and diagnostic entrypoint regression rules are present and non-blocking by default. +- **AC-375-010**: `git diff --check` passes; `pint --dirty` passes if PHP changes; the selected guard test/command passes in `warn` mode; validation report records all commands. +- **AC-375-011**: `follow-up-recommendations.md` names what should become CI-blocking later, what stays manual review, whether Evidence/System browser fixture coverage is still needed, and whether browser-scorecard integration is deferred. + +## Success Criteria + +- Future UI changes can be checked with one documented command/test before review. +- Customer/auditor default-surface raw ID/internal-term leaks are caught as blocking v1 failures. +- Ambiguous UI bloat patterns are visible without creating noisy blanket failures. +- Existing known debt is documented, not silently fixed or ignored. + +## Assumptions + +- Existing guard/test conventions under `apps/platform/tests/Feature/Guards`, `apps/platform/tests/Architecture`, `apps/platform/tests/Pest.php`, `apps/platform/tests/Support/TestLaneManifest.php`, and `scripts/check-ui-productization-coverage` are sufficient to implement the v1 guard without new infrastructure, provided heavy-governance ownership is registered explicitly or documented as targeted-only. +- Browser verification is intentionally unnecessary for this guard-only spec. +- Spec 370-374 artifacts remain available as input context; missing optional artifacts must be recorded as `not available`. + +## Risks + +- **False positives**: Mitigate by defaulting most heuristic rules to manual review, using allowlist policy, and limiting hard failures. +- **Guard ignored due to noise**: Mitigate with grouped findings, clear rule IDs, initial debt classification, and follow-up recommendations. +- **Overfitting current pages**: Mitigate by deriving rules from Spec 370-374 contracts rather than screenshots alone. +- **Blocking valid diagnostics**: Mitigate by treating diagnostic technicality as manual review unless customer/auditor leakage is clear. +- **Implementation drift into refactor**: Mitigate with explicit no-runtime-UI-refactor tasks and validation report. + +## Open Questions + +- None block preparation. The implementation pass must choose Pest guard vs Artisan command vs script after final repo discovery. + +## Follow-Up Spec Candidates + +- Spec 376 - Browser Audit Fixture Coverage for Evidence/System Surfaces v1. +- Browser scorecard integration for UI bloat guard output. +- UI bloat guard CI hard-fail expansion after allowlist cleanup. +- Final post-productization closeout browser audit after guard stabilization. diff --git a/specs/375-ui-bloat-regression-guard/tasks.md b/specs/375-ui-bloat-regression-guard/tasks.md new file mode 100644 index 00000000..49eafa1d --- /dev/null +++ b/specs/375-ui-bloat-regression-guard/tasks.md @@ -0,0 +1,136 @@ +# Tasks: Spec 375 - UI Bloat Regression Guard v1 + +**Input**: `specs/375-ui-bloat-regression-guard/spec.md`, `plan.md`, `checklists/requirements.md`, user-provided Spec 375 draft, Specs 368 and 370-374 artifacts, and current repo guard/test conventions. + +**Tests**: Required for later implementation. This spec adds repository guard/tooling behavior, not product UI behavior. + +## Test Governance Checklist + +- [x] Lane assignment is named and narrow: Heavy-Governance / `surface-guard` unless implementation proves a cheaper targeted guard lane. +- [x] New or changed tests stay in the smallest honest family; no browser, DB, workspace, tenant, provider, session, queue, or seed fixture is introduced. +- [x] Shared helpers and scanner fixtures stay cheap by default; no heavy application setup is hidden in a broad helper. +- [x] Planned validation commands cover the guard without pulling in unrelated suite cost. +- [x] No product page screenshot or browser smoke is required. +- [x] Any material runtime, lane, baseline, trend, or CI strictness impact is recorded in `artifacts/validation-report.md`. + +## Phase 1: Preparation And Repo Truth + +**Purpose**: Confirm Spec 375 is a guardrail implementation, not another UI refactor pass. + +- [x] T001 Re-read `specs/375-ui-bloat-regression-guard/spec.md`, `plan.md`, `tasks.md`, and `checklists/requirements.md`. +- [x] T002 Re-read source inputs from Spec 368 where available: + - `specs/368-platform-ui-signal-to-noise-browser-audit/audit.md` + - `specs/368-platform-ui-signal-to-noise-browser-audit/page-scorecard.csv` + - `specs/368-platform-ui-signal-to-noise-browser-audit/findings.md` + - `specs/368-platform-ui-signal-to-noise-browser-audit/spec-candidates.md` + - `specs/368-platform-ui-signal-to-noise-browser-audit/artifacts/raw/browser-notes.md` +- [x] T003 Re-read Spec 370 IA contract artifacts: + - `specs/370-global-surface-information-architecture-contract/artifacts/surface-contract.md` + - `specs/370-global-surface-information-architecture-contract/artifacts/ui-bloat-patterns.md` + - `specs/370-global-surface-information-architecture-contract/artifacts/page-assessment-checklist.md` + - `specs/370-global-surface-information-architecture-contract/artifacts/copy-and-terminology-rules.md` + - `specs/370-global-surface-information-architecture-contract/artifacts/follow-up-spec-map.md` +- [x] T004 Re-read completed Spec 371-374 artifacts needed for guard rule inputs, recording missing artifacts as `not available`: + - Spec 371 browser verification, page contracts, implementation notes, validation report + - Spec 372 browser verification, customer surface contracts, customer safety checklist, implementation notes, validation report + - Spec 373 browser verification, diagnostic surface contracts, diagnostic safety checklist, implementation notes, validation report + - `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/source-audit-summary.md` + - `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/diagnostic-entrypoint-matrix.md` + - `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/browser-verification-report.md` + - `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/implementation-notes.md` + - `specs/374-diagnostic-entry-point-support-diagnostics-consolidation/artifacts/validation-report.md` +- [x] T005 Inspect current guard/test conventions in `apps/platform/tests/Feature/Guards`, `apps/platform/tests/Architecture`, `apps/platform/tests/Pest.php`, `apps/platform/tests/Support/TestLaneManifest.php`, and `scripts/check-ui-productization-coverage`. +- [x] T006 Inspect current console command conventions in `apps/platform/app/Console/Commands` only to decide whether an Artisan command is narrower than a Pest guard. +- [x] T007 Create `specs/375-ui-bloat-regression-guard/artifacts/source-summary.md` with available/missing inputs, existing guard structures, repo-verified scan paths, absent path candidates, selected implementation option, rejected options, and verification classes. +- [x] T008 Confirm no migrations, models, policies, routes, Filament pages/resources, Livewire components, views, panel providers, Graph contracts, jobs, queues, scheduler, storage, or runtime UI files need intentional product behavior changes. + +## Phase 2: Spec-Local Guard Artifacts Before Tooling + +**Purpose**: Make the guard contract reviewable before implementation. + +- [x] T009 Create `specs/375-ui-bloat-regression-guard/artifacts/guard-rules.md` documenting all ten rule groups, rule IDs, purpose, patterns, surface applicability, strictness, default result, allowlist behavior, and examples. +- [x] T010 Create `specs/375-ui-bloat-regression-guard/artifacts/scanner-design.md` documenting scan scope, selected `apps/platform/app/Support` UI-support subpaths, absent path treatment, exclusions, file discovery, surface classification heuristic, pattern matching, strictness, report output, exit code behavior, allowlist storage path/format or explicit no-allowlist-file decision, limitations, and future improvements. +- [x] T011 Create `specs/375-ui-bloat-regression-guard/artifacts/allowlist-policy.md` with allowlist schema, concrete allowlist file path/format or explicit v1 no-allowlist-file decision, allowed reasons, forbidden blanket patterns, review/expiry expectations, and examples. +- [x] T012 Create `specs/375-ui-bloat-regression-guard/artifacts/affected-files.md` with planned file rows before implementation. +- [x] T013 Create `specs/375-ui-bloat-regression-guard/artifacts/validation-report.md` with branch, HEAD, dirty state before implementation, planned validation commands, and no-runtime-UI-refactor assertion. +- [x] T014 Create `specs/375-ui-bloat-regression-guard/artifacts/follow-up-recommendations.md` with planned sections for CI strictness, manual-review leftovers, Evidence/System browser fixtures, browser-scorecard integration, and post-productization closeout audit. + +## Phase 3: Tests First - Scanner Behavior + +**Purpose**: Prove guard semantics before scanning the real repo. + +- [x] T015 Add targeted Pest coverage or equivalent guard tests for strictness modes: `report`, `warn`, and `fail`, including exit/result behavior for blocking versus non-blocking findings. +- [x] T016 Add fixture/sample coverage for `UIBLOAT_CUSTOMER_RAW_ID` proving likely customer/auditor default files hard-fail on raw ID labels unless allowlisted. +- [x] T017 Add fixture/sample coverage for `UIBLOAT_CUSTOMER_INTERNAL_TERM` proving blocked internal/debug/provider terms hard-fail in customer/auditor default files unless allowlisted. +- [x] T018 Add fixture/sample coverage for `UIBLOAT_ZERO_METRIC_CARD` and `UIBLOAT_REPEATED_STATUS` proving ambiguous matches are warnings or manual-review findings, not v1 hard failures. +- [x] T019 Add fixture/sample coverage for `UIBLOAT_MISSING_PRIMARY_QUESTION`, `UIBLOAT_HEADER_ACTION_OVERLOAD`, `UIBLOAT_EVIDENCE_DIAGNOSTICS_MIXED`, and `UIBLOAT_TECH_METADATA_MAIN`. +- [x] T020 Add fixture/sample coverage for `UIBLOAT_DIAGNOSTIC_GUIDANCE_MISSING` and `UIBLOAT_DIAGNOSTIC_ENTRYPOINT_AMBIGUOUS` with manual-review default behavior. +- [x] T021 Add allowlist validation coverage proving entries require rule ID, file, pattern, reason, surface type, audience, review/expiry marker, and owner/spec. +- [x] T022 Add exclusion/separate-classification coverage proving routes, models, migrations, tests, specs, screenshots, generated reports, and translation dictionaries do not become runtime UI findings by default. + +## Phase 4: Guard Implementation + +**Purpose**: Implement exactly one repo-conform guard entrypoint. + +- [x] T023 Implement the chosen guard entrypoint, preferring `apps/platform/tests/Feature/Guards/UiBloatRegressionGuardTest.php` unless the source summary documents why an Artisan command or script is narrower. +- [x] T024 If a scanner helper is needed, keep it narrow and test-owned where practical; do not add a runtime product service or framework unless the source summary proves test-local code is insufficient. +- [x] T025 Configure initial scan paths: + - `apps/platform/app/Filament` + - `apps/platform/resources/views/filament` + - `apps/platform/app/Support/EnvironmentDashboard` + - `apps/platform/app/Support/Navigation` + - `apps/platform/app/Support/OpsUx` + - `apps/platform/app/Support/SupportDiagnostics` + - `apps/platform/app/Support/Ui` + - `apps/platform/app/Support/Workspaces` +- [x] T026 Configure exclusions for vendor, node modules, storage, build artifacts, generated reports, screenshots, specs, and non-runtime tests; record absent scan candidates such as `apps/platform/resources/views/components` and `apps/platform/app/View` as `not available` if still missing, and do not scan `apps/platform/app/Support` wholesale. +- [x] T027 Implement surface classification for likely customer/auditor, diagnostic/support, operator, and unknown files. +- [x] T028 Implement findings with rule ID, file, pattern, surface classification, severity/result, reason, suggested action, and allowlist status. +- [x] T029 Implement strictness behavior with `warn` as v1 default and non-zero result only for hard customer/auditor safety failures unless `fail` is intentionally selected. +- [x] T030 Register and validate lane ownership: if a Pest guard is selected, group it in `apps/platform/tests/Pest.php` and either add a `surface-guard` family/hotspot in `apps/platform/tests/Support/TestLaneManifest.php` for `test:heavy` discovery or document targeted-only ownership in `source-summary.md` and `validation-report.md`. +- [x] T031 If an Artisan command or repo script is chosen, add command/script validation and document why it is narrower than Pest; ensure it runs without database/browser/provider setup. + +## Phase 5: Initial Scan And Reporting + +**Purpose**: Establish initial repo state without fixing broad UI debt. + +- [x] T032 Run the selected guard in `warn` mode against the configured source paths. +- [x] T033 Create `specs/375-ui-bloat-regression-guard/artifacts/initial-scan-report.md` with command run, timestamp, files scanned, findings by rule, findings by severity, blocking failures, warnings, manual-review findings, allowlisted findings, known existing debt, false positives, and recommended follow-ups. +- [x] T034 If existing findings appear, classify them as known debt, manual review, false positive, or allowlisted; do not broad-refactor pages. +- [x] T035 If a clear customer/auditor hard failure appears in existing runtime UI, stop broad fixes and document whether it is in-scope as a small safety fix or deferred known debt; do not silently refactor. +- [x] T036 Update `artifacts/allowlist-policy.md` and the selected actual allowlist file only with scoped, reasoned entries; if no allowlist file is selected for v1, document the empty/no-allowlist decision instead. +- [x] T037 Update `artifacts/follow-up-recommendations.md` with CI strictness, browser-scorecard, Evidence/System fixture, and closeout audit recommendations. + +## Phase 6: Validation And Close-Out Artifacts + +**Purpose**: Finish with bounded proof and no runtime UI scope creep. + +- [x] T038 Run the exact targeted Pest guard test or selected command, plus targeted lane registration/placement validation when `apps/platform/tests/Pest.php` or `apps/platform/tests/Support/TestLaneManifest.php` changes. +- [x] T039 Run the guard in `warn` mode and confirm initial scan report generation. +- [x] T040 Run `cd apps/platform && php vendor/bin/pint --dirty` if PHP files changed. +- [x] T041 Run `git diff --check`. +- [x] T042 Complete `artifacts/affected-files.md` with final touched files, purpose, change type, runtime/tooling/spec classification, risk, and verification level. +- [x] T043 Complete `artifacts/validation-report.md` with branch, HEAD, dirty state before/after, commands run, guard result, initial scan result, tests run, `git diff --check`, Pint result if applicable, known limitations, and recommended next spec. +- [x] T044 Confirm final implementation response states Livewire v4 compliance, provider registration location, global search status, destructive-action safety, asset strategy, tests, and deployment impact. + +## Non-Goals Checklist + +- [x] NT001 Do not refactor runtime UI pages. +- [x] NT002 Do not modify Customer Review Workspace, Environment Review, Review Pack, Stored Report, Evidence Snapshot, OperationRun, Backup Set, Restore Run, Operations Hub, Environment Dashboard, Baseline Profile, Provider Connections, Environment Diagnostics, Required Permissions, System Panel, or diagnostic entrypoint runtime behavior. +- [x] NT003 Do not add migrations, models, persisted product truth, enum/status families, jobs, policies, routes, Livewire components, Filament pages/resources, navigation entries, or Graph calls. +- [x] NT004 Do not add screenshot diff infrastructure, full visual regression, broad browser audit, accessibility audit, or performance audit. +- [x] NT005 Do not make broad heuristic findings CI-blocking before allowlist cleanup. +- [x] NT006 Do not rewrite completed historical specs or remove implementation close-out/validation/browser evidence. + +## Dependencies And Execution Order + +- Phase 1 must complete before selecting implementation option. +- Phase 2 must complete before code/tooling edits. +- Phase 3 tests should precede scanner implementation. +- Phase 4 implements the narrow guard. +- Phase 5 runs initial scan and records debt without broad fixes. +- Phase 6 validates and closes artifacts. + +## Recommended Implementation Strategy + +Start with a Pest guard under `tests/Feature/Guards` because the repo already has many source-scan guard tests and lane classification for `surface-guard`. If it should run in `test:heavy`, register the guard in `tests/Pest.php` and `tests/Support/TestLaneManifest.php`; otherwise document targeted-only ownership. Only choose an Artisan command if source-summary evidence shows local/report ergonomics require command behavior now. Keep v1 warn-first and customer-safety-hard-fail-only.