'test', 'confidence' => 'test:confidence', 'browser' => 'test:browser', 'heavy-governance' => 'test:heavy', 'profiling' => 'test:profile', 'junit' => 'test:junit', ]; private const COMPARISON_BASELINES = [ 'shared-test-fixture-slimming' => [ 'fast-feedback' => [ 'laneId' => 'fast-feedback', 'finishedAt' => '2026-04-16T13:11:57+00:00', 'wallClockSeconds' => 176.73623, 'budgetThresholdSeconds' => 200, 'targetImprovementPercent' => 10, 'maxRegressionPercent' => 5, ], 'confidence' => [ 'laneId' => 'confidence', 'finishedAt' => '2026-04-16T13:11:57+00:00', 'wallClockSeconds' => 394.383441, 'budgetThresholdSeconds' => 450, 'targetImprovementPercent' => 10, 'maxRegressionPercent' => 5, ], ], ]; /** * @return array */ public static function manifest(): array { return [ 'version' => 1, 'artifactDirectory' => self::artifactDirectory(), 'classifications' => self::classifications(), 'families' => self::families(), 'mixedFileResolutions' => self::mixedFileResolutions(), 'placementRules' => self::placementRules(), 'driftGuards' => self::driftGuards(), 'budgetTargets' => self::budgetTargets(), 'lanes' => self::lanes(), 'familyBudgets' => self::familyBudgets(), ]; } /** * @return list> */ public static function classifications(): array { return [ [ 'classificationId' => 'ui-light', 'purpose' => 'Localized tenant or admin UI checks that keep narrow operator trust without broad discovery fan-out.', 'dominantCostDrivers' => ['single-mount', 'localized assertions'], 'defaultLaneId' => 'confidence', 'allowedLaneIds' => ['fast-feedback', 'confidence'], 'forbiddenLaneIds' => ['browser'], 'reviewerSignals' => [ 'single page or table surface', 'no broad discovery or reflection pass', 'bounded assertion count', ], 'escalationTriggers' => [ 'multi-surface workflow fan-out', 'relation-manager breadth', 'resource or action-surface discovery', ], ], [ 'classificationId' => 'ui-workflow', 'purpose' => 'Bounded operator workflows that still provide meaningful product trust but fan out across more than one surface or step.', 'dominantCostDrivers' => ['multi-mount workflow', 'fixture fan-out'], 'defaultLaneId' => 'confidence', 'allowedLaneIds' => ['confidence', 'heavy-governance'], 'forbiddenLaneIds' => ['fast-feedback', 'browser'], 'reviewerSignals' => [ 'multi-step wizard or matrix flow', 'shared fixture graphs that stay inside one product workflow', 'direct operator trust value even when setup is dense', ], 'escalationTriggers' => [ 'governance-wide discovery', 'repeated broad relation-manager scans', 'surface discipline across multiple unrelated resources', ], ], [ 'classificationId' => 'surface-guard', 'purpose' => 'Broad governance checks that validate action surfaces, relation-manager breadth, or navigation discipline across multiple components.', 'dominantCostDrivers' => ['surface-wide validation', 'relation-manager breadth', 'assertion density'], 'defaultLaneId' => 'heavy-governance', 'allowedLaneIds' => ['heavy-governance'], 'forbiddenLaneIds' => ['fast-feedback', 'confidence', 'browser'], 'reviewerSignals' => [ 'governance contract or discipline test', 'multiple actions or surfaces validated together', 'broad relation-manager or navigation coverage', ], 'escalationTriggers' => [ 'action inventory discovery', 'header or navigation discipline spanning many resources', 'matrix or relation-manager breadth beyond one local workflow', ], ], [ 'classificationId' => 'discovery-heavy', 'purpose' => 'Discovery, reflection, or remembered-context scans that widen cost as the Filament surface grows.', 'dominantCostDrivers' => ['resource discovery', 'reflection', 'broad search or registry scans'], 'defaultLaneId' => 'heavy-governance', 'allowedLaneIds' => ['heavy-governance'], 'forbiddenLaneIds' => ['fast-feedback', 'confidence', 'browser'], 'reviewerSignals' => [ 'reflection-backed discovery', 'resource or global-search parity scans', 'remembered tenant or registry breadth dominating runtime', ], 'escalationTriggers' => [ 'new resources or pages increase the touched surface', 'broad remembered-context validation', 'registry or relationship discovery beyond one feature', ], ], [ 'classificationId' => 'browser', 'purpose' => 'Real browser smoke and workflow coverage that remains fully isolated from non-browser lanes.', 'dominantCostDrivers' => ['real-browser interaction', 'end-to-end rendering'], 'defaultLaneId' => 'browser', 'allowedLaneIds' => ['browser'], 'forbiddenLaneIds' => ['fast-feedback', 'confidence', 'heavy-governance', 'profiling', 'junit'], 'reviewerSignals' => [ 'visit() or browser assertions', 'real browser session state', 'JavaScript or DOM continuity checks', ], 'escalationTriggers' => [ 'browser runtime or DOM interactivity is required', 'cross-page journey depends on real browser semantics', 'client-side behavior is part of the contract', ], ], ]; } /** * @return array */ public static function classification(string $classificationId): array { foreach (self::classifications() as $classification) { if ($classification['classificationId'] === $classificationId) { return $classification; } } throw new InvalidArgumentException(sprintf('Unknown heavy test classification [%s].', $classificationId)); } /** * @return list> */ public static function families(): array { return [ [ 'familyId' => 'backup-set-admin-tenant-parity', 'classificationId' => 'ui-light', 'purpose' => 'Keep remembered admin tenant scoping honest on a localized backup-set listing surface.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'confidence', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'ui-light', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Document the retained localized UI confidence anchor.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Filament/BackupSetAdminTenantParityTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical ui-light confidence example for Spec 208.', ], ], 'hotspotFiles' => [ 'tests/Feature/Filament/BackupSetAdminTenantParityTest.php', ], 'costSignals' => ['single-mount', 'remembered tenant state', 'localized table assertions'], 'confidenceRationale' => 'This remains in Confidence because it protects a narrow remembered-tenant table surface without broad discovery cost.', 'validationStatus' => 'guarded', ], [ 'familyId' => 'baseline-compare-matrix-workflow', 'classificationId' => 'ui-workflow', 'purpose' => 'Validate the bounded baseline compare matrix workflow, including visible-assignment fan-out and dense matrix rendering.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'confidence', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'ui-workflow', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Keep the matrix workflow explicit as retained confidence coverage.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Seeded matrix workflow action hotspot.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Seeded matrix workflow builder hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php', 'tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php', 'tests/Feature/Concerns/BuildsBaselineCompareMatrixFixtures.php', ], 'costSignals' => ['workflow fan-out', 'dense fixture graph', 'multi-tenant matrix rendering'], 'confidenceRationale' => 'The compare matrix remains a primary operator workflow and stays bounded to one feature surface even when fixtures are dense.', 'validationStatus' => 'guarded', ], [ 'familyId' => 'onboarding-wizard-enforcement', 'classificationId' => 'ui-workflow', 'purpose' => 'Preserve the managed tenant onboarding wizard capability flow as an intentional retained workflow check.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'confidence', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'ui-workflow', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Retain bounded wizard trust in Confidence with explicit classification.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Rbac/OnboardingWizardUiEnforcementTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Seeded onboarding workflow hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Rbac/OnboardingWizardUiEnforcementTest.php', ], 'costSignals' => ['multi-step wizard flow', 'repeated Livewire mounts', 'workspace capability transitions'], 'confidenceRationale' => 'The onboarding wizard is a high-value operator path that should remain in Confidence despite its bounded workflow cost.', 'validationStatus' => 'guarded', ], [ 'familyId' => 'finding-bulk-actions-workflow', 'classificationId' => 'ui-workflow', 'purpose' => 'Escalate the large finding bulk-action workflow once multi-action fan-out and audit density stop fitting the standard contributor loops.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'ui-workflow', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Keep the bulk finding workflow explicitly cataloged as workflow-heavy even after it leaves the standard lanes.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Findings/FindingBulkActionsTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical high-fan-out finding bulk workflow hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Findings/FindingBulkActionsTest.php', ], 'costSignals' => ['bulk table actions', 'multi-action workflow fan-out', 'high-volume audit assertions'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'drift-bulk-triage-all-matching', 'classificationId' => 'ui-workflow', 'purpose' => 'Escalate the all-matching drift triage confirmation flow once it operates over a large result set and broad action fan-out.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'ui-workflow', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Keep the all-matching drift triage confirmation flow classified as workflow-heavy while routing it intentionally to Heavy Governance.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical all-matching drift triage hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php', ], 'costSignals' => ['all-matching bulk action', 'typed confirmation flow', 'large result-set workflow'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'baseline-profile-start-surfaces', 'classificationId' => 'ui-workflow', 'purpose' => 'Escalate the baseline profile capture and compare start surfaces once repeated header-action gating and queued-operation setup dominate runtime.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'ui-workflow', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Keep the baseline profile start surfaces classified as workflow-heavy while routing them intentionally to Heavy Governance.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical baseline compare start-surface hotspot.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical baseline capture start-surface hotspot.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Filament/BaselineActionAuthorizationTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical baseline action authorization hotspot tied to the same start-surface family.', ], ], 'hotspotFiles' => [ 'tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php', 'tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php', 'tests/Feature/Filament/BaselineActionAuthorizationTest.php', ], 'costSignals' => ['repeated Livewire page mounts', 'header action gating matrix', 'queued-operation launch workflow'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'workspace-settings-slice-management', 'classificationId' => 'ui-workflow', 'purpose' => 'Escalate the multi-slice workspace settings management surface once one test covers a broad save, reset, and resolver verification workflow across several settings domains.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'ui-workflow', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Keep the workspace settings management surface classified as a broad workflow-heavy check while routing it out of Confidence.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical workspace settings multi-slice hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php', ], 'costSignals' => ['multi-slice form workflow', 'save-and-reset path coverage', 'post-write resolver verification fan-out'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'findings-workflow-surfaces', 'classificationId' => 'ui-workflow', 'purpose' => 'Escalate the remaining row, view, filter, and renewal findings workflow surfaces once their multi-step state transitions and evidence handling dominate confidence runtime.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'ui-workflow', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Keep the findings workflow surface explicitly cataloged as workflow-heavy while routing it out of Confidence.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Findings/FindingWorkflowRowActionsTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical row-action workflow hotspot.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Findings/FindingWorkflowViewActionsTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical view-header workflow hotspot.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Findings/FindingsListFiltersTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical findings filter workflow hotspot.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Findings/FindingExceptionRenewalTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical exception renewal workflow hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Findings/FindingWorkflowRowActionsTest.php', 'tests/Feature/Findings/FindingWorkflowViewActionsTest.php', 'tests/Feature/Findings/FindingsListFiltersTest.php', 'tests/Feature/Findings/FindingExceptionRenewalTest.php', ], 'costSignals' => ['multi-step workflow transitions', 'filter-state persistence', 'evidence and audit verification'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'workspace-only-admin-surface-independence', 'classificationId' => 'surface-guard', 'purpose' => 'Escalate cross-resource workspace-only admin surface independence checks because they validate broad remembered-tenant behavior across several Filament resources together.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'surface-guard', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Treat workspace-only cross-resource independence as a broad surface guard.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Filament/WorkspaceOnlySurfaceTenantIndependenceTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical workspace-only admin surface independence hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Filament/WorkspaceOnlySurfaceTenantIndependenceTest.php', ], 'costSignals' => ['cross-resource surface assertions', 'remembered tenant context variance', 'workspace-only admin invariants'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'action-surface-contract', 'classificationId' => 'surface-guard', 'purpose' => 'Guard broad action-surface discovery and declaration discipline across pages, resources, and relation managers.', 'currentLaneId' => 'heavy-governance', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'surface-guard', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Flag broad governance validation separately from retained workflows.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Guards/ActionSurfaceContractTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Canonical seeded surface-guard hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Guards/ActionSurfaceContractTest.php', ], 'costSignals' => ['resource discovery', 'surface-wide validation', 'broad assertion density'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'policy-resource-admin-search-parity', 'classificationId' => 'discovery-heavy', 'purpose' => 'Verify remembered canonical admin tenant state never leaks policy global-search discovery.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'discovery-heavy', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Explicitly classify broad admin search parity as discovery-heavy.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Seeded policy resource discovery hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php', ], 'costSignals' => ['reflection', 'global-search discovery', 'remembered admin tenant context'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'policy-version-admin-search-parity', 'classificationId' => 'discovery-heavy', 'purpose' => 'Keep policy-version admin search parity classified as discovery-heavy when remembered context broadens the surface.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'discovery-heavy', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Treat policy-version parity as the same discovery-heavy family class as resource parity.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Seeded policy-version discovery hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php', ], 'costSignals' => ['reflection', 'global-search discovery', 'remembered admin tenant context'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'backup-items-relation-manager-enforcement', 'classificationId' => 'surface-guard', 'purpose' => 'Protect broad relation-manager authorization affordances for backup item actions from drifting into lighter lanes.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'surface-guard', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Relation-manager breadth belongs to the surface-guard class.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Seeded backup items relation-manager hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php', ], 'costSignals' => ['relation-manager breadth', 'table action matrix', 'tenant capability transitions'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'workspace-memberships-relation-manager-enforcement', 'classificationId' => 'surface-guard', 'purpose' => 'Keep workspace membership relation-manager action discipline classified as broad surface governance.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'surface-guard', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Workspace membership relation-manager breadth is a deliberate surface guard.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Rbac/WorkspaceMembershipsRelationManagerUiEnforcementTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Seeded workspace memberships relation-manager hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Rbac/WorkspaceMembershipsRelationManagerUiEnforcementTest.php', ], 'costSignals' => ['relation-manager breadth', 'action visibility matrix', 'workspace membership transitions'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'tenant-review-header-discipline', 'classificationId' => 'surface-guard', 'purpose' => 'Enforce primary and grouped header action discipline for tenant review pages.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'surface-guard', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Header action discipline spans a broad action surface.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Filament/TenantReviewHeaderDisciplineTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Seeded header discipline hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Filament/TenantReviewHeaderDisciplineTest.php', ], 'costSignals' => ['header action discipline', 'grouped action inventory', 'multi-state surface assertions'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'panel-navigation-segregation', 'classificationId' => 'surface-guard', 'purpose' => 'Keep panel navigation segregation and tenant-sensitive registration checks in the heavy governance lane.', 'currentLaneId' => 'confidence', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'surface-guard', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Navigation discipline is a broad surface-guard concern.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Feature/Filament/PanelNavigationSegregationTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Seeded navigation discipline hotspot.', ], ], 'hotspotFiles' => [ 'tests/Feature/Filament/PanelNavigationSegregationTest.php', ], 'costSignals' => ['navigation registration breadth', 'panel boot', 'tenant-sensitive surface assertions'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'ops-ux-governance', 'classificationId' => 'surface-guard', 'purpose' => 'Keep established Ops UX, alert header, and credential leak governance checks under the heavy-governance umbrella.', 'currentLaneId' => 'heavy-governance', 'targetLaneId' => 'heavy-governance', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'surface-guard', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Treat the established Ops UX batch as broad governance coverage.', ], [ 'selectorType' => 'path', 'selectorValue' => 'tests/Feature/OpsUx', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Existing broad Ops UX directory remains heavy-governance owned.', ], ], 'hotspotFiles' => [ 'tests/Feature/Filament/Alerts/AlertsKpiHeaderTest.php', 'tests/Feature/Guards/OperationLifecycleOpsUxGuardTest.php', 'tests/Feature/ProviderConnections/CredentialLeakGuardTest.php', ], 'costSignals' => ['ops-ux governance breadth', 'cross-surface workflow coverage', 'alert and credential guard fan-out'], 'validationStatus' => 'guarded', ], [ 'familyId' => 'browser-smoke', 'classificationId' => 'browser', 'purpose' => 'Isolate browser smoke and real DOM continuity checks in the dedicated browser lane.', 'currentLaneId' => 'browser', 'targetLaneId' => 'browser', 'selectors' => [ [ 'selectorType' => 'group', 'selectorValue' => 'browser', 'selectorRole' => 'include', 'sourceOfTruth' => 'pest-group', 'rationale' => 'Keep browser ownership explicit at the group layer.', ], [ 'selectorType' => 'path', 'selectorValue' => 'tests/Browser', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Attribute the browser lane to its dedicated suite path.', ], [ 'selectorType' => 'file', 'selectorValue' => 'tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php', 'selectorRole' => 'inventory-only', 'sourceOfTruth' => 'manifest', 'rationale' => 'Seeded browser hotspot for Spec 208.', ], ], 'hotspotFiles' => [ 'tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php', ], 'costSignals' => ['real-browser interaction', 'dense DOM continuity', 'end-to-end smoke'], 'validationStatus' => 'guarded', ], ]; } /** * @return array */ public static function family(string $familyId): array { foreach (self::families() as $family) { if ($family['familyId'] === $familyId) { return $family; } } throw new InvalidArgumentException(sprintf('Unknown heavy test family [%s].', $familyId)); } /** * @return list> */ public static function familiesByTargetLane(string $laneId): array { return array_values(array_filter( self::families(), static fn (array $family): bool => $family['targetLaneId'] === $laneId, )); } /** * @return list> */ public static function mixedFileResolutions(): array { return [ [ 'filePath' => 'tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php', 'primaryClassificationId' => 'discovery-heavy', 'secondaryClassificationIds' => ['ui-light'], 'resolutionStrategy' => 'broadest-cost-wins', 'rationale' => 'The file reads like a narrow admin check but remembered tenant search parity and reflection-backed discovery dominate the runtime shape.', 'followUpRequired' => true, ], [ 'filePath' => 'tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php', 'primaryClassificationId' => 'ui-workflow', 'secondaryClassificationIds' => ['discovery-heavy'], 'resolutionStrategy' => 'broadest-cost-wins', 'rationale' => 'The builder is dense and discovery aware, but it still primarily protects the bounded compare-matrix workflow that Confidence keeps.', 'followUpRequired' => true, ], [ 'filePath' => 'tests/Feature/Concerns/BuildsBaselineCompareMatrixFixtures.php', 'primaryClassificationId' => 'ui-workflow', 'secondaryClassificationIds' => ['discovery-heavy', 'surface-guard'], 'resolutionStrategy' => 'broadest-cost-wins', 'rationale' => 'The fixture trait amplifies the compare-matrix workflow family until a narrower extraction is justified.', 'followUpRequired' => true, ], [ 'filePath' => 'tests/Feature/Concerns/BuildsPortfolioTriageFixtures.php', 'primaryClassificationId' => 'surface-guard', 'secondaryClassificationIds' => ['ui-workflow'], 'resolutionStrategy' => 'broadest-cost-wins', 'rationale' => 'The portfolio triage fixture graph widens cross-tenant scope and should inherit the broadest governance-oriented cost signal until split.', 'followUpRequired' => true, ], ]; } /** * @return array|null */ public static function mixedFileResolution(string $filePath): ?array { foreach (self::mixedFileResolutions() as $resolution) { if ($resolution['filePath'] === $filePath) { return $resolution; } } return null; } /** * @return list> */ public static function placementRules(): array { return [ [ 'ruleId' => 'ui-light-fast-feedback-allowed', 'classificationId' => 'ui-light', 'laneId' => 'fast-feedback', 'allowance' => 'allowed', 'reason' => 'Explicitly approved localized UI checks may remain in Fast Feedback when discovery stays bounded.', ], [ 'ruleId' => 'ui-light-confidence-required', 'classificationId' => 'ui-light', 'laneId' => 'confidence', 'allowance' => 'required', 'reason' => 'Confidence keeps localized UI trust as its lightest retained UI layer.', ], [ 'ruleId' => 'ui-workflow-confidence-required', 'classificationId' => 'ui-workflow', 'laneId' => 'confidence', 'allowance' => 'required', 'reason' => 'Selected bounded workflows remain in Confidence to preserve real operator trust.', ], [ 'ruleId' => 'ui-workflow-heavy-governance-allowed', 'classificationId' => 'ui-workflow', 'laneId' => 'heavy-governance', 'allowance' => 'allowed', 'reason' => 'Workflow families may escalate into Heavy Governance when their breadth stops being local.', ], [ 'ruleId' => 'surface-guard-heavy-governance-required', 'classificationId' => 'surface-guard', 'laneId' => 'heavy-governance', 'allowance' => 'required', 'reason' => 'Broad surface guards belong in Heavy Governance by default.', ], [ 'ruleId' => 'surface-guard-fast-feedback-forbidden', 'classificationId' => 'surface-guard', 'laneId' => 'fast-feedback', 'allowance' => 'forbidden', 'reason' => 'Fast Feedback must never carry broad surface-guard breadth.', ], [ 'ruleId' => 'surface-guard-confidence-forbidden', 'classificationId' => 'surface-guard', 'laneId' => 'confidence', 'allowance' => 'forbidden', 'reason' => 'Confidence should retain bounded workflows, not governance-wide surface discipline.', ], [ 'ruleId' => 'discovery-heavy-heavy-governance-required', 'classificationId' => 'discovery-heavy', 'laneId' => 'heavy-governance', 'allowance' => 'required', 'reason' => 'Discovery-heavy scans scale with surface breadth and therefore belong in Heavy Governance.', ], [ 'ruleId' => 'discovery-heavy-fast-feedback-forbidden', 'classificationId' => 'discovery-heavy', 'laneId' => 'fast-feedback', 'allowance' => 'forbidden', 'reason' => 'Fast Feedback must stay free of discovery-heavy scans.', ], [ 'ruleId' => 'discovery-heavy-confidence-forbidden', 'classificationId' => 'discovery-heavy', 'laneId' => 'confidence', 'allowance' => 'forbidden', 'reason' => 'Confidence keeps explicit retained UI trust, not remembered-context discovery scans.', ], [ 'ruleId' => 'browser-browser-required', 'classificationId' => 'browser', 'laneId' => 'browser', 'allowance' => 'required', 'reason' => 'Browser coverage remains isolated in the dedicated browser lane.', ], [ 'ruleId' => 'browser-fast-feedback-forbidden', 'classificationId' => 'browser', 'laneId' => 'fast-feedback', 'allowance' => 'forbidden', 'reason' => 'Browser coverage cannot run in Fast Feedback.', ], [ 'ruleId' => 'browser-confidence-forbidden', 'classificationId' => 'browser', 'laneId' => 'confidence', 'allowance' => 'forbidden', 'reason' => 'Browser coverage cannot run in Confidence.', ], [ 'ruleId' => 'browser-heavy-governance-forbidden', 'classificationId' => 'browser', 'laneId' => 'heavy-governance', 'allowance' => 'forbidden', 'reason' => 'Browser coverage does not merge into Heavy Governance.', ], ]; } /** * @return list> */ public static function driftGuards(): array { return [ [ 'guardId' => 'browser-lane-isolation', 'scope' => 'classification', 'assertionType' => 'browser-isolation', 'targetRefs' => ['browser'], 'owningTestPaths' => ['tests/Feature/Guards/BrowserLaneIsolationTest.php'], 'failureContract' => 'Failures must name the browser file or family that leaked into a non-browser lane.', ], [ 'guardId' => 'discovery-heavy-fast-feedback-exclusion', 'scope' => 'lane', 'assertionType' => 'forbidden-membership', 'targetRefs' => ['discovery-heavy', 'fast-feedback'], 'owningTestPaths' => [ 'tests/Feature/Guards/FastFeedbackLaneContractTest.php', 'tests/Feature/Guards/FastFeedbackLaneExclusionTest.php', ], 'failureContract' => 'Failures must name the discovery-heavy family or file still present in Fast Feedback.', ], [ 'guardId' => 'surface-guard-fast-feedback-exclusion', 'scope' => 'lane', 'assertionType' => 'forbidden-membership', 'targetRefs' => ['surface-guard', 'fast-feedback'], 'owningTestPaths' => [ 'tests/Feature/Guards/FastFeedbackLaneContractTest.php', 'tests/Feature/Guards/FastFeedbackLaneExclusionTest.php', ], 'failureContract' => 'Failures must identify the broad surface-guard file or family that still leaks into Fast Feedback.', ], [ 'guardId' => 'confidence-ui-workflow-whitelist', 'scope' => 'lane', 'assertionType' => 'required-membership', 'targetRefs' => ['ui-light', 'ui-workflow', 'confidence'], 'owningTestPaths' => [ 'tests/Feature/Guards/ConfidenceLaneContractTest.php', 'tests/Feature/Guards/HeavyGovernanceLaneContractTest.php', ], 'failureContract' => 'Failures must name the confidence family whose classification or rationale is inconsistent with the retained confidence catalog.', ], [ 'guardId' => 'heavy-attribution-contract', 'scope' => 'report', 'assertionType' => 'required-attribution', 'targetRefs' => ['surface-guard', 'discovery-heavy', 'browser'], 'owningTestPaths' => [ 'tests/Feature/Guards/TestLaneArtifactsContractTest.php', 'tests/Feature/Guards/ProfileLaneContractTest.php', ], 'failureContract' => 'Failures must name the missing classification or family attribution payload under storage/logs/test-lanes.', ], ]; } /** * @return list> */ public static function budgetTargets(): array { return [ [ 'budgetId' => 'lane-heavy-governance', 'targetType' => 'lane', 'targetId' => 'heavy-governance', 'thresholdSeconds' => 200, 'baselineSource' => 'measured-lane', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'refresh when heavy family ownership changes', ], [ 'budgetId' => 'classification-ui-workflow', 'targetType' => 'classification', 'targetId' => 'ui-workflow', 'thresholdSeconds' => 150, 'baselineSource' => 'measured-current-suite', 'enforcement' => 'report-only', 'lifecycleState' => 'draft', 'reviewCadence' => 'tighten after the retained confidence families stabilize', ], [ 'budgetId' => 'classification-surface-guard', 'targetType' => 'classification', 'targetId' => 'surface-guard', 'thresholdSeconds' => 90, 'baselineSource' => 'measured-post-spec-207', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable heavy-governance runs', ], [ 'budgetId' => 'classification-discovery-heavy', 'targetType' => 'classification', 'targetId' => 'discovery-heavy', 'thresholdSeconds' => 60, 'baselineSource' => 'measured-post-spec-207', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable heavy-governance runs', ], [ 'budgetId' => 'classification-browser', 'targetType' => 'classification', 'targetId' => 'browser', 'thresholdSeconds' => 150, 'baselineSource' => 'measured-lane', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable browser runs', ], [ 'budgetId' => 'family-backup-set-admin-tenant-parity', 'targetType' => 'family', 'targetId' => 'backup-set-admin-tenant-parity', 'thresholdSeconds' => 20, 'baselineSource' => 'measured-current-suite', 'enforcement' => 'report-only', 'lifecycleState' => 'draft', 'reviewCadence' => 'tighten if more localized admin parity families are cataloged', ], [ 'budgetId' => 'family-baseline-compare-matrix-workflow', 'targetType' => 'family', 'targetId' => 'baseline-compare-matrix-workflow', 'thresholdSeconds' => 75, 'baselineSource' => 'measured-current-suite', 'enforcement' => 'report-only', 'lifecycleState' => 'draft', 'reviewCadence' => 'tighten after confidence workflow coverage settles', ], [ 'budgetId' => 'family-onboarding-wizard-enforcement', 'targetType' => 'family', 'targetId' => 'onboarding-wizard-enforcement', 'thresholdSeconds' => 45, 'baselineSource' => 'measured-current-suite', 'enforcement' => 'report-only', 'lifecycleState' => 'draft', 'reviewCadence' => 'tighten after confidence workflow coverage settles', ], [ 'budgetId' => 'family-finding-bulk-actions-workflow', 'targetType' => 'family', 'targetId' => 'finding-bulk-actions-workflow', 'thresholdSeconds' => 90, 'baselineSource' => 'measured-lane', 'enforcement' => 'report-only', 'lifecycleState' => 'draft', 'reviewCadence' => 'tighten after escalated heavy workflows stabilize', ], [ 'budgetId' => 'family-drift-bulk-triage-all-matching', 'targetType' => 'family', 'targetId' => 'drift-bulk-triage-all-matching', 'thresholdSeconds' => 25, 'baselineSource' => 'measured-lane', 'enforcement' => 'report-only', 'lifecycleState' => 'draft', 'reviewCadence' => 'tighten after escalated heavy workflows stabilize', ], [ 'budgetId' => 'family-baseline-profile-start-surfaces', 'targetType' => 'family', 'targetId' => 'baseline-profile-start-surfaces', 'thresholdSeconds' => 140, 'baselineSource' => 'measured-lane', 'enforcement' => 'report-only', 'lifecycleState' => 'draft', 'reviewCadence' => 'tighten after escalated heavy workflows stabilize', ], [ 'budgetId' => 'family-workspace-settings-slice-management', 'targetType' => 'family', 'targetId' => 'workspace-settings-slice-management', 'thresholdSeconds' => 70, 'baselineSource' => 'measured-lane', 'enforcement' => 'report-only', 'lifecycleState' => 'draft', 'reviewCadence' => 'tighten after escalated heavy workflows stabilize', ], [ 'budgetId' => 'family-findings-workflow-surfaces', 'targetType' => 'family', 'targetId' => 'findings-workflow-surfaces', 'thresholdSeconds' => 130, 'baselineSource' => 'measured-lane', 'enforcement' => 'report-only', 'lifecycleState' => 'draft', 'reviewCadence' => 'tighten after escalated heavy workflows stabilize', ], [ 'budgetId' => 'family-workspace-only-admin-surface-independence', 'targetType' => 'family', 'targetId' => 'workspace-only-admin-surface-independence', 'thresholdSeconds' => 40, 'baselineSource' => 'measured-lane', 'enforcement' => 'report-only', 'lifecycleState' => 'draft', 'reviewCadence' => 'tighten after escalated heavy workflows stabilize', ], [ 'budgetId' => 'family-action-surface-contract', 'targetType' => 'family', 'targetId' => 'action-surface-contract', 'thresholdSeconds' => 35, 'baselineSource' => 'measured-post-spec-207', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable heavy-governance runs', ], [ 'budgetId' => 'family-policy-resource-admin-search-parity', 'targetType' => 'family', 'targetId' => 'policy-resource-admin-search-parity', 'thresholdSeconds' => 20, 'baselineSource' => 'measured-post-spec-207', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable heavy-governance runs', ], [ 'budgetId' => 'family-policy-version-admin-search-parity', 'targetType' => 'family', 'targetId' => 'policy-version-admin-search-parity', 'thresholdSeconds' => 20, 'baselineSource' => 'measured-post-spec-207', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable heavy-governance runs', ], [ 'budgetId' => 'family-backup-items-relation-manager-enforcement', 'targetType' => 'family', 'targetId' => 'backup-items-relation-manager-enforcement', 'thresholdSeconds' => 30, 'baselineSource' => 'measured-post-spec-207', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable heavy-governance runs', ], [ 'budgetId' => 'family-workspace-memberships-relation-manager-enforcement', 'targetType' => 'family', 'targetId' => 'workspace-memberships-relation-manager-enforcement', 'thresholdSeconds' => 25, 'baselineSource' => 'measured-post-spec-207', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable heavy-governance runs', ], [ 'budgetId' => 'family-tenant-review-header-discipline', 'targetType' => 'family', 'targetId' => 'tenant-review-header-discipline', 'thresholdSeconds' => 20, 'baselineSource' => 'measured-post-spec-207', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable heavy-governance runs', ], [ 'budgetId' => 'family-panel-navigation-segregation', 'targetType' => 'family', 'targetId' => 'panel-navigation-segregation', 'thresholdSeconds' => 20, 'baselineSource' => 'measured-post-spec-207', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable heavy-governance runs', ], [ 'budgetId' => 'family-ops-ux-governance', 'targetType' => 'family', 'targetId' => 'ops-ux-governance', 'thresholdSeconds' => 120, 'baselineSource' => 'measured-current-suite', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable runs', ], [ 'budgetId' => 'family-browser-smoke', 'targetType' => 'family', 'targetId' => 'browser-smoke', 'thresholdSeconds' => 150, 'baselineSource' => 'measured-lane', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable runs', ], ]; } /** * @return list> */ public static function familyBudgets(): array { $familyBudgets = []; foreach (self::budgetTargets() as $budgetTarget) { if (($budgetTarget['targetType'] ?? null) !== 'family') { continue; } $family = self::family((string) $budgetTarget['targetId']); $selectors = array_values(array_unique(array_merge( self::budgetSelectorsForFamily($family), self::familyHotspotTestFiles($family), ))); $familyBudgets[] = array_merge($budgetTarget, [ 'familyId' => $budgetTarget['targetId'], 'selectorType' => self::primaryBudgetSelectorType($family), 'selectors' => $selectors, ]); } return $familyBudgets; } /** * @return array|null */ public static function budgetTarget(string $targetType, string $targetId): ?array { foreach (self::budgetTargets() as $budgetTarget) { if ($budgetTarget['targetType'] === $targetType && $budgetTarget['targetId'] === $targetId) { return $budgetTarget; } } return null; } /** * @return array */ public static function lane(string $id): array { foreach (self::lanes() as $lane) { if ($lane['id'] === $id) { return $lane; } } throw new InvalidArgumentException(sprintf('Unknown test lane [%s].', $id)); } /** * @return list> */ public static function lanes(): array { return [ [ 'id' => 'fast-feedback', 'governanceClass' => 'fast', 'description' => 'Quick representative feedback for normal local edits.', 'intendedAudience' => 'Contributors working in the default authoring loop.', 'includedFamilies' => ['unit', 'core-feature-safety', 'ui-light'], 'excludedFamilies' => ['browser', 'surface-guard', 'discovery-heavy', 'heavy-governance'], 'ownershipExpectations' => 'Run on normal edits and escalate to confidence or heavy-governance when the touched surface expands.', 'defaultEntryPoint' => true, 'parallelMode' => 'required', 'selectors' => [ 'includeSuites' => ['Unit'], 'includePaths' => [ 'tests/Feature/Auth', 'tests/Feature/Authorization', 'tests/Feature/EntraAdminRoles', 'tests/Feature/Findings', 'tests/Feature/Guards', 'tests/Feature/Monitoring', 'tests/Feature/Navigation', 'tests/Feature/Onboarding', 'tests/Feature/RequiredPermissions', 'tests/Feature/Tenants', 'tests/Feature/Workspaces', ], 'includeGroups' => [], 'includeFiles' => [], 'excludeSuites' => ['Browser'], 'excludePaths' => [ 'tests/Architecture', 'tests/Browser', 'tests/Deprecation', 'tests/Feature/078', 'tests/Feature/090', 'tests/Feature/144', 'tests/Feature/OpsUx', ], 'excludeGroups' => [], 'excludeFiles' => self::disallowedFamilyFilesForLane('fast-feedback'), ], 'artifacts' => ['summary', 'junit-xml', 'budget-report'], 'budget' => [ 'thresholdSeconds' => 200, 'baselineSource' => 'measured-current-suite', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'baselineDeltaTargetPercent' => 50, 'reviewCadence' => 'tighten after two stable runs', ], 'dbStrategy' => [ 'connectionMode' => 'sqlite-memory', 'resetStrategy' => 'refresh-database', 'seedsPolicy' => 'restricted', 'schemaBaselineCandidate' => false, ], ], [ 'id' => 'confidence', 'governanceClass' => 'confidence', 'description' => 'Broader pre-merge validation for non-browser feature and integration work.', 'intendedAudience' => 'Contributors and reviewers preparing a higher-confidence run before merge.', 'includedFamilies' => ['unit', 'non-browser-feature-integration', 'ui-light', 'ui-workflow'], 'excludedFamilies' => ['browser', 'surface-guard', 'discovery-heavy', 'heavy-governance'], 'ownershipExpectations' => 'Run before merge when a change touches multiple feature surfaces or shared infrastructure.', 'defaultEntryPoint' => false, 'parallelMode' => 'required', 'selectors' => [ 'includeSuites' => ['Unit'], 'includePaths' => ['tests/Feature'], 'includeGroups' => [], 'includeFiles' => self::laneHotspotTestFiles('confidence'), 'excludeSuites' => ['Browser'], 'excludePaths' => [ 'tests/Architecture', 'tests/Browser', 'tests/Deprecation', 'tests/Feature/078', 'tests/Feature/090', 'tests/Feature/144', 'tests/Feature/OpsUx', ], 'excludeGroups' => [], 'excludeFiles' => self::disallowedFamilyFilesForLane('confidence'), ], 'artifacts' => ['summary', 'junit-xml', 'budget-report'], 'budget' => [ 'thresholdSeconds' => 450, 'baselineSource' => 'measured-current-suite', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable runs', ], 'dbStrategy' => [ 'connectionMode' => 'sqlite-memory', 'resetStrategy' => 'refresh-database', 'seedsPolicy' => 'restricted', 'schemaBaselineCandidate' => false, ], ], [ 'id' => 'browser', 'governanceClass' => 'heavy', 'description' => 'Dedicated browser runtime lane for smoke and workflow validation.', 'intendedAudience' => 'Contributors validating browser-specific behavior and smoke coverage.', 'includedFamilies' => ['browser'], 'excludedFamilies' => ['fast-feedback', 'confidence', 'heavy-governance'], 'ownershipExpectations' => 'Run when a change touches browser behavior or before promoting browser-sensitive work.', 'defaultEntryPoint' => false, 'parallelMode' => 'forbidden', 'selectors' => [ 'includeSuites' => ['Browser'], 'includePaths' => ['tests/Browser'], 'includeGroups' => [], 'includeFiles' => [], 'excludeSuites' => [], 'excludePaths' => [], 'excludeGroups' => [], 'excludeFiles' => [], ], 'artifacts' => ['summary', 'junit-xml', 'budget-report'], 'budget' => [ 'thresholdSeconds' => 150, 'baselineSource' => 'measured-lane', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable runs', ], 'dbStrategy' => [ 'connectionMode' => 'sqlite-memory', 'resetStrategy' => 'refresh-database', 'seedsPolicy' => 'restricted', 'schemaBaselineCandidate' => false, ], ], [ 'id' => 'heavy-governance', 'governanceClass' => 'heavy', 'description' => 'Intentionally expensive governance scans, discovery-heavy parity checks, and high-fan-out operational validations.', 'intendedAudience' => 'Maintainers and reviewers validating the heaviest governance families on purpose.', 'includedFamilies' => ['architecture-governance', 'ops-ux', 'ui-workflow', 'surface-guard', 'discovery-heavy'], 'excludedFamilies' => ['browser', 'ui-light'], 'ownershipExpectations' => 'Run intentionally when touching governance scans, discovery-heavy parity, or broad UI contract families.', 'defaultEntryPoint' => false, 'parallelMode' => 'optional', 'selectors' => [ 'includeSuites' => [], 'includePaths' => [ 'tests/Architecture', 'tests/Deprecation', 'tests/Feature/078', 'tests/Feature/090', 'tests/Feature/144', 'tests/Feature/OpsUx', ], 'includeGroups' => [], 'includeFiles' => self::laneHotspotTestFiles('heavy-governance'), 'excludeSuites' => ['Browser'], 'excludePaths' => [], 'excludeGroups' => [], 'excludeFiles' => [], ], 'artifacts' => ['summary', 'junit-xml', 'budget-report'], 'budget' => [ 'thresholdSeconds' => 300, 'baselineSource' => 'measured-lane', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable runs', ], 'dbStrategy' => [ 'connectionMode' => 'mixed', 'resetStrategy' => 'refresh-database', 'seedsPolicy' => 'restricted', 'schemaBaselineCandidate' => false, ], ], [ 'id' => 'profiling', 'governanceClass' => 'support', 'description' => 'Serial non-browser profiling support run that exposes the slowest tests and family attribution.', 'intendedAudience' => 'Maintainers investigating slow-test drift before it becomes baseline behavior.', 'includedFamilies' => ['unit', 'non-browser-feature-integration', 'ui-workflow', 'surface-guard', 'discovery-heavy'], 'excludedFamilies' => ['browser'], 'ownershipExpectations' => 'Run intentionally when refreshing baselines or explaining which heavy families own the cost.', 'defaultEntryPoint' => false, 'parallelMode' => 'forbidden', 'selectors' => [ 'includeSuites' => ['Unit'], 'includePaths' => ['tests/Feature'], 'includeGroups' => [], 'includeFiles' => [], 'excludeSuites' => ['Browser'], 'excludePaths' => ['tests/Browser'], 'excludeGroups' => [], 'excludeFiles' => [], ], 'artifacts' => ['summary', 'junit-xml', 'profile-top', 'budget-report'], 'budget' => [ 'thresholdSeconds' => 3000, 'baselineSource' => 'measured-current-suite', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable runs', ], 'dbStrategy' => [ 'connectionMode' => 'sqlite-memory', 'resetStrategy' => 'refresh-database', 'seedsPolicy' => 'restricted', 'schemaBaselineCandidate' => false, ], ], [ 'id' => 'junit', 'governanceClass' => 'support', 'description' => 'Machine-readable report run for the confidence-shaped non-browser scope.', 'intendedAudience' => 'Contributors and reviewers who need durable artifacts instead of ad-hoc terminal output.', 'includedFamilies' => ['unit', 'non-browser-feature-integration', 'ui-light', 'ui-workflow'], 'excludedFamilies' => ['browser', 'surface-guard', 'discovery-heavy', 'heavy-governance'], 'ownershipExpectations' => 'Run when the latest confidence-shaped machine-readable report needs to be shared or attached to follow-up work.', 'defaultEntryPoint' => false, 'parallelMode' => 'required', 'selectors' => [ 'includeSuites' => ['Unit'], 'includePaths' => ['tests/Feature'], 'includeGroups' => [], 'includeFiles' => self::laneHotspotTestFiles('confidence'), 'excludeSuites' => ['Browser'], 'excludePaths' => [ 'tests/Architecture', 'tests/Browser', 'tests/Deprecation', 'tests/Feature/078', 'tests/Feature/090', 'tests/Feature/144', 'tests/Feature/OpsUx', ], 'excludeGroups' => [], 'excludeFiles' => self::disallowedFamilyFilesForLane('junit'), ], 'artifacts' => ['summary', 'junit-xml', 'budget-report'], 'budget' => [ 'thresholdSeconds' => 450, 'baselineSource' => 'measured-lane', 'enforcement' => 'warn', 'lifecycleState' => 'documented', 'reviewCadence' => 'tighten after two stable runs', ], 'dbStrategy' => [ 'connectionMode' => 'sqlite-memory', 'resetStrategy' => 'refresh-database', 'seedsPolicy' => 'restricted', 'schemaBaselineCandidate' => false, ], ], ]; } public static function commandRef(string $laneId): string { if (! array_key_exists($laneId, self::COMMAND_REFS)) { throw new InvalidArgumentException(sprintf('Unknown lane command reference [%s].', $laneId)); } return self::COMMAND_REFS[$laneId]; } public static function artifactDirectory(): string { return self::ARTIFACT_DIRECTORY; } public static function fullSuiteBaselineSeconds(): int { return self::FULL_SUITE_BASELINE_SECONDS; } /** * @return array|null */ public static function comparisonBaseline(string $comparisonProfile, string $laneId): ?array { $profileBaselines = self::COMPARISON_BASELINES[$comparisonProfile] ?? null; if (! is_array($profileBaselines)) { return null; } $baseline = $profileBaselines[$laneId] ?? null; return is_array($baseline) ? $baseline : null; } /** * @return list */ public static function buildCommand(string $laneId): array { $lane = self::lane($laneId); $commandTargets = self::commandTargets($laneId, $lane); $command = ['php', 'vendor/bin/pest', '--colors=never']; $configurationPath = $commandTargets !== [] ? self::writeLaneConfiguration($laneId, $commandTargets) : null; $commandSuites = $commandTargets === [] ? self::commandSuites($lane) : []; if (! in_array('profile-top', $lane['artifacts'], true)) { $command[] = '--compact'; } if ($lane['parallelMode'] === 'required') { $command[] = '--parallel'; } if ($configurationPath !== null) { $command[] = '--configuration='.$configurationPath; $command[] = '--testsuite=Lane'; } elseif ($commandSuites !== []) { $command[] = '--testsuite='.implode(',', $commandSuites); } $command[] = '--log-junit='.TestLaneReport::artifactPaths($laneId)['junit']; if (in_array('profile-top', $lane['artifacts'], true)) { $command[] = '--profile'; } if ($commandTargets === []) { foreach ($lane['selectors']['includeGroups'] as $group) { $command[] = '--group='.$group; } foreach ($lane['selectors']['excludeGroups'] as $group) { $command[] = '--exclude-group='.$group; } } return $command; } public static function runLane(string $laneId): int { self::ensureArtifactDirectory(); $command = self::buildCommand($laneId); $process = new Process($command, self::appRoot()); $process->setTimeout(null); $capturedOutput = ''; $startedAt = microtime(true); $process->run(function (string $type, string $buffer) use (&$capturedOutput): void { echo $buffer; $capturedOutput .= $buffer; }); TestLaneReport::finalizeLane($laneId, microtime(true) - $startedAt, $capturedOutput); return $process->getExitCode() ?? 1; } public static function renderLatestReport(string $laneId, ?string $comparisonProfile = null): int { $artifactPaths = TestLaneReport::artifactPaths($laneId); $reportPath = self::absolutePath($artifactPaths['report']); $wallClockSeconds = 0.0; if (is_file($reportPath)) { $existingReport = json_decode((string) file_get_contents($reportPath), true); $wallClockSeconds = (float) ($existingReport['wallClockSeconds'] ?? 0.0); } $parsed = TestLaneReport::parseJUnit(self::absolutePath($artifactPaths['junit']), $laneId); $profileOutputPath = self::absolutePath($artifactPaths['profile']); $report = TestLaneReport::buildReport( laneId: $laneId, wallClockSeconds: $wallClockSeconds, slowestEntries: $parsed['slowestEntries'], durationsByFile: $parsed['durationsByFile'], comparisonProfile: $comparisonProfile, ); TestLaneReport::writeArtifacts( laneId: $laneId, report: $report, profileOutput: is_file($profileOutputPath) ? (string) file_get_contents($profileOutputPath) : null, ); echo json_encode($report, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR).PHP_EOL; return 0; } /** * @return list */ public static function discoverFiles(string $laneId): array { $lane = self::lane($laneId); $selectors = $lane['selectors']; $files = []; foreach (array_merge(self::suiteTargets($selectors['includeSuites']), $selectors['includePaths'], $selectors['includeFiles']) as $target) { foreach (self::discoverTarget($target) as $resolvedFile) { if (self::isExcluded($resolvedFile, $selectors, $laneId)) { continue; } $files[] = $resolvedFile; } } $files = array_values(array_unique($files)); sort($files); return $files; } public static function absolutePath(string $relativePath): string { return self::appRoot().DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, ltrim($relativePath, '/')); } public static function laneConfigurationPath(string $laneId, ?string $artifactDirectory = null): string { $directory = trim($artifactDirectory ?? self::artifactDirectory(), '/'); return sprintf('%s/%s-lane.phpunit.xml', $directory, $laneId); } /** * @param array|null $family * @return array|null */ public static function familyForFile(string $filePath, ?array $family = null): ?array { if (is_array($family)) { return self::familyMatchesFile($family, $filePath) ? $family : null; } $matchedFamilies = []; foreach (self::families() as $candidate) { if (! self::familyMatchesFile($candidate, $filePath)) { continue; } $matchedFamilies[] = [ 'family' => $candidate, 'score' => self::familyMatchScore($candidate, $filePath), ]; } if ($matchedFamilies === []) { return null; } usort($matchedFamilies, static fn (array $left, array $right): int => $right['score'] <=> $left['score']); return $matchedFamilies[0]['family']; } /** * @return array */ public static function validateLanePlacement( string $laneId, ?string $classificationId = null, ?string $familyId = null, ?string $filePath = null, array $observedCostSignals = [], ): array { $family = is_string($familyId) ? self::family($familyId) : null; if ($family === null && is_string($filePath)) { $family = self::familyForFile($filePath); } $mixedResolution = is_string($filePath) ? self::mixedFileResolution($filePath) : null; $resolvedClassificationId = $classificationId ?? ($mixedResolution['primaryClassificationId'] ?? null) ?? ($family['classificationId'] ?? null); if (! is_string($resolvedClassificationId) || $resolvedClassificationId === '') { throw new InvalidArgumentException('Lane placement validation requires a classification, family, or resolvable file path.'); } $classification = self::classification($resolvedClassificationId); $rule = self::placementRuleFor($resolvedClassificationId, $laneId); $allowance = $rule['allowance'] ?? (in_array($laneId, $classification['allowedLaneIds'], true) ? 'allowed' : (in_array($laneId, $classification['forbiddenLaneIds'], true) ? 'forbidden' : ($classification['defaultLaneId'] === $laneId ? 'required' : 'discouraged'))); $reasons = []; if (is_array($rule)) { $reasons[] = (string) $rule['reason']; } if (is_array($family)) { $reasons[] = sprintf( 'Family [%s] is cataloged as [%s] and targets lane [%s].', $family['familyId'], $family['classificationId'], $family['targetLaneId'], ); } if (is_array($mixedResolution)) { $reasons[] = sprintf( 'Mixed-file resolution [%s] applies [%s] via [%s].', $mixedResolution['filePath'], $mixedResolution['primaryClassificationId'], $mixedResolution['resolutionStrategy'], ); } if ($observedCostSignals !== []) { $reasons[] = sprintf('Observed cost signals: %s.', implode(', ', $observedCostSignals)); } $valid = $allowance !== 'forbidden'; if (is_array($family) && ! in_array($laneId, self::allowedFamilyTargetLanesForLane($laneId), true)) { $valid = false; } if (is_array($family) && ! in_array($family['targetLaneId'], self::allowedFamilyTargetLanesForLane($laneId), true)) { $allowance = 'forbidden'; $valid = false; $reasons[] = sprintf( 'Family [%s] targets [%s], which is outside the allowed family targets for lane [%s].', $family['familyId'], $family['targetLaneId'], $laneId, ); } $remediationOptions = []; if (! $valid) { $remediationOptions[] = sprintf( 'Move the file or family into [%s].', is_array($family) ? $family['targetLaneId'] : $classification['defaultLaneId'], ); $remediationOptions[] = sprintf('Tag the surface with [%s] or [%s] groups as appropriate.', $resolvedClassificationId, is_array($family) ? $family['targetLaneId'] : $classification['defaultLaneId']); } return array_filter([ 'laneId' => $laneId, 'resolvedClassificationId' => $resolvedClassificationId, 'familyId' => $family['familyId'] ?? null, 'allowance' => $allowance, 'valid' => $valid, 'reasons' => $reasons, 'remediationOptions' => $remediationOptions, 'mixedFileResolution' => $mixedResolution, ], static fn (mixed $value): bool => $value !== null && $value !== []); } public static function describeFilePlacement(string $filePath): string { $family = self::familyForFile($filePath); if ($family === null) { return sprintf('File [%s] is not cataloged in the Spec 208 family inventory.', $filePath); } $mixedResolution = self::mixedFileResolution($filePath); $message = sprintf( 'File [%s] resolves to family [%s] in class [%s] targeting lane [%s].', $filePath, $family['familyId'], $family['classificationId'], $family['targetLaneId'], ); if (is_array($mixedResolution)) { $message .= sprintf( ' Mixed-file strategy [%s] keeps [%s] as the primary class.', $mixedResolution['resolutionStrategy'], $mixedResolution['primaryClassificationId'], ); } return $message; } private static function appRoot(): string { return dirname(__DIR__, 2); } /** * @param list $targets */ private static function writeLaneConfiguration(string $laneId, array $targets): string { $relativePath = self::laneConfigurationPath($laneId); $absolutePath = self::absolutePath($relativePath); self::ensureDirectory(dirname($absolutePath)); $xml = new \DOMDocument('1.0', 'UTF-8'); $xml->preserveWhiteSpace = false; $xml->formatOutput = true; $xml->load(self::absolutePath('phpunit.xml')); $phpunit = $xml->documentElement; if (! $phpunit instanceof \DOMElement) { throw new \RuntimeException('Unable to load phpunit.xml root element.'); } $phpunit->setAttribute('bootstrap', self::absolutePath('vendor/autoload.php')); foreach (iterator_to_array($phpunit->childNodes) as $childNode) { if ($childNode instanceof \DOMElement && $childNode->tagName === 'testsuites') { $phpunit->removeChild($childNode); } } foreach (iterator_to_array($phpunit->getElementsByTagName('directory')) as $directoryNode) { if (trim($directoryNode->textContent) === 'app') { $directoryNode->nodeValue = self::absolutePath('app'); } } $testsuites = $xml->createElement('testsuites'); $testsuite = $xml->createElement('testsuite'); $testsuite->setAttribute('name', 'Lane'); foreach ($targets as $target) { $testsuite->appendChild($xml->createElement('file', self::absolutePath($target))); } $testsuites->appendChild($testsuite); $insertBefore = null; foreach (iterator_to_array($phpunit->childNodes) as $childNode) { if ($childNode instanceof \DOMElement && in_array($childNode->tagName, ['source', 'php'], true)) { $insertBefore = $childNode; break; } } if ($insertBefore instanceof \DOMElement) { $phpunit->insertBefore($testsuites, $insertBefore); } else { $phpunit->appendChild($testsuites); } $xml->save($absolutePath); return $relativePath; } private static function ensureArtifactDirectory(): void { $directory = self::absolutePath(self::artifactDirectory()); if (is_dir($directory)) { return; } mkdir($directory, 0777, true); } private static function ensureDirectory(string $directory): void { if (is_dir($directory)) { return; } mkdir($directory, 0777, true); } /** * @param list $suites * @return list */ private static function suiteTargets(array $suites): array { $targets = []; foreach ($suites as $suite) { $targets = array_merge($targets, match ($suite) { 'Unit' => ['tests/Unit'], 'Feature' => ['tests/Feature'], 'Browser' => ['tests/Browser'], 'Pgsql' => ['tests/Feature/WorkspaceIsolation/WorkspaceIdForeignKeyConstraintTest.php'], default => [], }); } return array_values(array_unique($targets)); } /** * @param array $lane * @return list */ private static function commandTargets(string $laneId, array $lane): array { $targets = self::discoverFiles($laneId); if ($targets !== []) { return $targets; } return []; } /** * @param array $lane * @return list */ private static function commandSuites(array $lane): array { $selectors = $lane['selectors']; $suites = []; foreach ($selectors['includeSuites'] as $suite) { $suites[] = $suite; } foreach (array_merge($selectors['includePaths'], $selectors['includeFiles']) as $target) { $suite = self::inferSuiteFromTarget($target); if ($suite !== null) { $suites[] = $suite; } } $suites = array_values(array_unique(array_filter($suites, static fn (mixed $suite): bool => is_string($suite) && $suite !== ''))); if ($selectors['excludeSuites'] !== []) { $suites = array_values(array_filter( $suites, static fn (string $suite): bool => ! in_array($suite, $selectors['excludeSuites'], true), )); } $suiteOrder = [ 'Unit' => 10, 'Feature' => 20, 'Browser' => 30, 'Architecture' => 40, 'Deprecation' => 50, 'Pgsql' => 60, ]; usort($suites, static function (string $left, string $right) use ($suiteOrder): int { return ($suiteOrder[$left] ?? 999) <=> ($suiteOrder[$right] ?? 999); }); return $suites; } private static function inferSuiteFromTarget(string $target): ?string { return match (true) { str_starts_with($target, 'tests/Unit') => 'Unit', str_starts_with($target, 'tests/Feature') => 'Feature', str_starts_with($target, 'tests/Browser') => 'Browser', str_starts_with($target, 'tests/Architecture') => 'Architecture', str_starts_with($target, 'tests/Deprecation') => 'Deprecation', default => null, }; } /** * @return list */ private static function discoverTarget(string $target): array { $absoluteTarget = self::absolutePath($target); if (is_file($absoluteTarget)) { return [str_replace(DIRECTORY_SEPARATOR, '/', $target)]; } if (! is_dir($absoluteTarget)) { return []; } $files = []; $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($absoluteTarget)); /** @var SplFileInfo $file */ foreach ($iterator as $file) { if (! $file->isFile() || ! str_ends_with($file->getFilename(), 'Test.php')) { continue; } $resolvedPath = str_replace(self::appRoot().DIRECTORY_SEPARATOR, '', $file->getPathname()); $files[] = str_replace(DIRECTORY_SEPARATOR, '/', $resolvedPath); } return $files; } /** * @param array $selectors */ private static function isExcluded(string $filePath, array $selectors, string $laneId): bool { if (in_array($filePath, $selectors['excludeFiles'], true)) { return true; } foreach ($selectors['excludePaths'] as $excludedPath) { if (str_starts_with($filePath, rtrim($excludedPath, '/'))) { return true; } } $family = self::familyForFile($filePath); if ($family === null) { return false; } return ! in_array($family['targetLaneId'], self::allowedFamilyTargetLanesForLane($laneId), true); } /** * @return array|null */ private static function placementRuleFor(string $classificationId, string $laneId): ?array { foreach (self::placementRules() as $rule) { if ($rule['classificationId'] === $classificationId && $rule['laneId'] === $laneId) { return $rule; } } return null; } /** * @return list */ private static function allowedFamilyTargetLanesForLane(string $laneId): array { return match ($laneId) { 'fast-feedback' => ['fast-feedback'], 'confidence', 'junit' => ['fast-feedback', 'confidence'], 'heavy-governance' => ['heavy-governance'], 'browser' => ['browser'], 'profiling' => ['fast-feedback', 'confidence', 'heavy-governance'], default => [$laneId], }; } /** * @return list */ private static function disallowedFamilyFilesForLane(string $laneId): array { $allowedTargets = self::allowedFamilyTargetLanesForLane($laneId); $files = []; foreach (self::families() as $family) { if (in_array($family['targetLaneId'], $allowedTargets, true)) { continue; } $files = array_merge($files, self::familyHotspotTestFiles($family)); } $files = array_values(array_unique($files)); sort($files); return $files; } /** * @return list */ private static function laneHotspotTestFiles(string $laneId): array { $files = []; foreach (self::familiesByTargetLane($laneId) as $family) { $files = array_merge($files, self::familyHotspotTestFiles($family)); } $files = array_values(array_unique($files)); sort($files); return $files; } /** * @param array $family * @return list */ private static function familyHotspotTestFiles(array $family): array { return array_values(array_filter( $family['hotspotFiles'], static fn (mixed $filePath): bool => is_string($filePath) && str_ends_with($filePath, 'Test.php'), )); } /** * @param array $family */ private static function primaryBudgetSelectorType(array $family): string { foreach ($family['selectors'] as $selector) { if (($selector['selectorType'] ?? null) === 'path' && in_array($selector['selectorRole'] ?? 'include', ['include', 'inventory-only'], true)) { return 'path'; } } return 'file'; } /** * @param array $family * @return list */ private static function budgetSelectorsForFamily(array $family): array { $selectors = []; foreach ($family['selectors'] as $selector) { if (! in_array($selector['selectorRole'] ?? 'include', ['include', 'inventory-only'], true)) { continue; } if (! in_array($selector['selectorType'] ?? 'file', ['file', 'path'], true)) { continue; } $selectors[] = (string) $selector['selectorValue']; } return array_values(array_unique($selectors)); } /** * @param array $family */ private static function familyMatchesFile(array $family, string $filePath): bool { if (in_array($filePath, $family['hotspotFiles'], true)) { return true; } foreach ($family['selectors'] as $selector) { if (! in_array($selector['selectorRole'] ?? 'include', ['include', 'inventory-only'], true)) { continue; } $selectorType = (string) ($selector['selectorType'] ?? 'file'); $selectorValue = (string) ($selector['selectorValue'] ?? ''); if ($selectorValue === '') { continue; } $matches = match ($selectorType) { 'file' => $filePath === $selectorValue, 'path' => str_starts_with($filePath, rtrim($selectorValue, '/')), default => false, }; if ($matches) { return true; } } return false; } /** * @param array $family */ private static function familyMatchScore(array $family, string $filePath): int { $score = 0; foreach ($family['selectors'] as $selector) { if (! in_array($selector['selectorRole'] ?? 'include', ['include', 'inventory-only'], true)) { continue; } $selectorType = (string) ($selector['selectorType'] ?? 'file'); $selectorValue = (string) ($selector['selectorValue'] ?? ''); if ($selectorType === 'file' && $selectorValue === $filePath) { $score = max($score, 2_000 + strlen($selectorValue)); } if ($selectorType === 'path' && str_starts_with($filePath, rtrim($selectorValue, '/'))) { $score = max($score, 1_000 + strlen($selectorValue)); } } if (in_array($filePath, $family['hotspotFiles'], true)) { $score = max($score, 1_500 + strlen($filePath)); } return $score; } }