Spec 208: finalize heavy suite segmentation #241
3
.github/agents/copilot-instructions.md
vendored
3
.github/agents/copilot-instructions.md
vendored
@ -194,6 +194,7 @@ ## Active Technologies
|
||||
- SQLite `:memory:` for the default test configuration, dedicated PostgreSQL config for the schema-level `Pgsql` suite, and local runner artifacts under `apps/platform/storage/logs/test-lanes` (206-test-suite-governance)
|
||||
- PHP 8.4.15 + Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail (207-shared-test-fixture-slimming)
|
||||
- SQLite `:memory:` for the default test environment, isolated PostgreSQL coverage via the existing dedicated suite, and lane-measurement artifacts under the app-root contract path `storage/logs/test-lanes` (207-shared-test-fixture-slimming)
|
||||
- SQLite `:memory:` for the default test environment, existing lane artifacts under the app-root contract path `storage/logs/test-lanes`, and no new product persistence (208-heavy-suite-segmentation)
|
||||
|
||||
- PHP 8.4.15 (feat/005-bulk-operations)
|
||||
|
||||
@ -228,8 +229,8 @@ ## Code Style
|
||||
PHP 8.4.15: Follow standard conventions
|
||||
|
||||
## Recent Changes
|
||||
- 208-heavy-suite-segmentation: Added PHP 8.4.15 + Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail
|
||||
- 207-shared-test-fixture-slimming: Added PHP 8.4.15 + Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail
|
||||
- 206-test-suite-governance: Added PHP 8.4.15 + Laravel 12, Pest v4, PHPUnit 12, Pest Browser plugin, Filament v5, Livewire v4, Laravel Sail
|
||||
- 198-monitoring-page-state: Added PHP 8.4.15 + Laravel 12, Filament v5, Livewire v4, Pest v4, Tailwind CSS v4, existing `CanonicalAdminTenantFilterState`, `CanonicalNavigationContext`, `OperateHubShell`, Filament `InteractsWithTable`, and page-local Livewire state on the affected Filament pages
|
||||
<!-- MANUAL ADDITIONS START -->
|
||||
<!-- MANUAL ADDITIONS END -->
|
||||
|
||||
@ -80,6 +80,21 @@
|
||||
"@php artisan config:clear --ansi",
|
||||
"@php -r \"require 'vendor/autoload.php'; exit(\\Tests\\Support\\TestLaneManifest::runLane('junit'));\""
|
||||
],
|
||||
"test:report": [
|
||||
"@php -r \"require 'vendor/autoload.php'; exit(\\Tests\\Support\\TestLaneManifest::renderLatestReport('fast-feedback', 'shared-test-fixture-slimming'));\""
|
||||
],
|
||||
"test:report:confidence": [
|
||||
"@php -r \"require 'vendor/autoload.php'; exit(\\Tests\\Support\\TestLaneManifest::renderLatestReport('confidence', 'shared-test-fixture-slimming'));\""
|
||||
],
|
||||
"test:report:browser": [
|
||||
"@php -r \"require 'vendor/autoload.php'; exit(\\Tests\\Support\\TestLaneManifest::renderLatestReport('browser', ''));\""
|
||||
],
|
||||
"test:report:heavy": [
|
||||
"@php -r \"require 'vendor/autoload.php'; exit(\\Tests\\Support\\TestLaneManifest::renderLatestReport('heavy-governance', ''));\""
|
||||
],
|
||||
"test:report:profile": [
|
||||
"@php -r \"require 'vendor/autoload.php'; exit(\\Tests\\Support\\TestLaneManifest::renderLatestReport('profiling', ''));\""
|
||||
],
|
||||
"test:pgsql": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"@php vendor/bin/pest -c phpunit.pgsql.xml"
|
||||
|
||||
@ -5,19 +5,35 @@
|
||||
use Illuminate\Support\Collection;
|
||||
use Tests\Support\TestLaneManifest;
|
||||
|
||||
it('keeps browser tests isolated behind their dedicated lane', function (): void {
|
||||
it('keeps browser tests isolated behind their dedicated lane and class', function (): void {
|
||||
$lane = TestLaneManifest::lane('browser');
|
||||
$files = new Collection(TestLaneManifest::discoverFiles('browser'));
|
||||
$validation = TestLaneManifest::validateLanePlacement(
|
||||
laneId: 'browser',
|
||||
filePath: 'tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php',
|
||||
);
|
||||
|
||||
expect($lane['includedFamilies'])->toContain('browser')
|
||||
->and($lane['defaultEntryPoint'])->toBeFalse()
|
||||
->and($files)->not->toBeEmpty()
|
||||
->and($files->every(static fn (string $path): bool => str_starts_with($path, 'tests/Browser/')))->toBeTrue();
|
||||
->and($files->every(static fn (string $path): bool => str_starts_with($path, 'tests/Browser/')))->toBeTrue()
|
||||
->and($validation['valid'])->toBeTrue()
|
||||
->and($validation['resolvedClassificationId'])->toBe('browser')
|
||||
->and($validation['familyId'])->toBe('browser-smoke');
|
||||
});
|
||||
|
||||
it('keeps the browser lane routed to the browser suite and out of the default loops', function (): void {
|
||||
expect(TestLaneManifest::buildCommand('browser'))->toContain('--group=browser')
|
||||
->and(TestLaneManifest::buildCommand('browser'))->toContain('--testsuite=Browser');
|
||||
it('rejects browser placement in non-browser lanes and keeps the default loops clean', function (): void {
|
||||
$misplaced = TestLaneManifest::validateLanePlacement(
|
||||
laneId: 'confidence',
|
||||
filePath: 'tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php',
|
||||
);
|
||||
$configurationPath = TestLaneManifest::laneConfigurationPath('browser');
|
||||
$configurationContents = (string) file_get_contents(TestLaneManifest::absolutePath($configurationPath));
|
||||
|
||||
expect(TestLaneManifest::buildCommand('browser'))->toContain('--configuration='.$configurationPath)
|
||||
->and($configurationContents)->toContain('tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php')
|
||||
->and($misplaced['valid'])->toBeFalse()
|
||||
->and($misplaced['allowance'])->toBe('forbidden');
|
||||
|
||||
foreach (TestLaneManifest::discoverFiles('fast-feedback') as $path) {
|
||||
expect($path)->not->toStartWith('tests/Browser/');
|
||||
|
||||
@ -4,24 +4,69 @@
|
||||
|
||||
use Tests\Support\TestLaneManifest;
|
||||
|
||||
it('keeps confidence broader than fast-feedback while excluding browser and heavy-governance', function (): void {
|
||||
it('keeps confidence broader than fast-feedback while excluding browser and moved heavy families', function (): void {
|
||||
$lane = TestLaneManifest::lane('confidence');
|
||||
$command = TestLaneManifest::buildCommand('confidence');
|
||||
$configurationPath = TestLaneManifest::laneConfigurationPath('confidence');
|
||||
$configurationContents = (string) file_get_contents(TestLaneManifest::absolutePath($configurationPath));
|
||||
|
||||
expect($lane['parallelMode'])->toBe('required')
|
||||
->and($lane['includedFamilies'])->toContain('unit', 'non-browser-feature-integration')
|
||||
->and($lane['excludedFamilies'])->toContain('browser', 'heavy-governance')
|
||||
->and($lane['includedFamilies'])->toContain('unit', 'ui-light', 'ui-workflow')
|
||||
->and($lane['excludedFamilies'])->toContain('browser', 'surface-guard', 'discovery-heavy')
|
||||
->and($lane['budget']['thresholdSeconds'])->toBeLessThan(TestLaneManifest::fullSuiteBaselineSeconds())
|
||||
->and($command)->toContain('--parallel')
|
||||
->and($command)->toContain('--testsuite=Unit,Feature')
|
||||
->and(implode(' ', $command))->toContain('--exclude-group=browser,heavy-governance');
|
||||
->and($command)->toContain('--configuration='.$configurationPath)
|
||||
->and($command)->toContain('--testsuite=Lane')
|
||||
->and($configurationContents)->toContain('tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/Rbac/OnboardingWizardUiEnforcementTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Filament/BaselineActionAuthorizationTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Findings/FindingBulkActionsTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Findings/FindingExceptionRenewalTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Findings/FindingsListFiltersTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Findings/FindingWorkflowRowActionsTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Findings/FindingWorkflowViewActionsTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Filament/WorkspaceOnlySurfaceTenantIndependenceTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Guards/ActionSurfaceContractTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php');
|
||||
});
|
||||
|
||||
it('keeps confidence discovery free of the initial heavy-governance batch', function (): void {
|
||||
it('retains only documented ui-light and selected ui-workflow families in confidence discovery', function (): void {
|
||||
$files = TestLaneManifest::discoverFiles('confidence');
|
||||
$confidenceFamilies = TestLaneManifest::familiesByTargetLane('confidence');
|
||||
|
||||
expect($files)->not->toContain('tests/Feature/Guards/ActionSurfaceContractTest.php')
|
||||
->and($files)->not->toContain('tests/Feature/Guards/OperationLifecycleOpsUxGuardTest.php')
|
||||
->and($files)->not->toContain('tests/Feature/ProviderConnections/CredentialLeakGuardTest.php')
|
||||
->and($files)->not->toContain('tests/Feature/Filament/Alerts/AlertsKpiHeaderTest.php');
|
||||
expect($files)->toContain(
|
||||
'tests/Feature/Filament/BackupSetAdminTenantParityTest.php',
|
||||
'tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php',
|
||||
'tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php',
|
||||
'tests/Feature/Rbac/OnboardingWizardUiEnforcementTest.php',
|
||||
)
|
||||
->and($files)->not->toContain(
|
||||
'tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php',
|
||||
'tests/Feature/Filament/BaselineActionAuthorizationTest.php',
|
||||
'tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php',
|
||||
'tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php',
|
||||
'tests/Feature/Findings/FindingBulkActionsTest.php',
|
||||
'tests/Feature/Findings/FindingExceptionRenewalTest.php',
|
||||
'tests/Feature/Findings/FindingsListFiltersTest.php',
|
||||
'tests/Feature/Findings/FindingWorkflowRowActionsTest.php',
|
||||
'tests/Feature/Findings/FindingWorkflowViewActionsTest.php',
|
||||
'tests/Feature/Guards/ActionSurfaceContractTest.php',
|
||||
'tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php',
|
||||
'tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php',
|
||||
'tests/Feature/Filament/WorkspaceOnlySurfaceTenantIndependenceTest.php',
|
||||
'tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php',
|
||||
'tests/Feature/Rbac/WorkspaceMembershipsRelationManagerUiEnforcementTest.php',
|
||||
'tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php',
|
||||
'tests/Feature/Filament/TenantReviewHeaderDisciplineTest.php',
|
||||
'tests/Feature/Filament/PanelNavigationSegregationTest.php',
|
||||
);
|
||||
|
||||
foreach ($confidenceFamilies as $family) {
|
||||
expect($family['classificationId'])->toBeIn(['ui-light', 'ui-workflow'])
|
||||
->and(trim((string) ($family['confidenceRationale'] ?? '')))->not->toBe('');
|
||||
}
|
||||
});
|
||||
@ -4,26 +4,39 @@
|
||||
|
||||
use Tests\Support\TestLaneManifest;
|
||||
|
||||
it('keeps fast-feedback as the default parallel contributor loop', function (): void {
|
||||
it('keeps fast-feedback as the default parallel contributor loop with explicit heavy exclusions', function (): void {
|
||||
$lane = TestLaneManifest::lane('fast-feedback');
|
||||
$command = TestLaneManifest::buildCommand('fast-feedback');
|
||||
$configurationPath = TestLaneManifest::laneConfigurationPath('fast-feedback');
|
||||
$configurationContents = (string) file_get_contents(TestLaneManifest::absolutePath($configurationPath));
|
||||
|
||||
expect($lane['defaultEntryPoint'])->toBeTrue()
|
||||
->and($lane['parallelMode'])->toBe('required')
|
||||
->and($lane['includedFamilies'])->toContain('unit')
|
||||
->and($lane['excludedFamilies'])->toContain('browser', 'heavy-governance')
|
||||
->and($lane['includedFamilies'])->toContain('unit', 'ui-light')
|
||||
->and($lane['excludedFamilies'])->toContain('browser', 'surface-guard', 'discovery-heavy')
|
||||
->and($lane['budget']['baselineDeltaTargetPercent'])->toBe(50)
|
||||
->and(TestLaneManifest::commandRef('fast-feedback'))->toBe('test')
|
||||
->and($command)->toContain('--group=fast-feedback')
|
||||
->and($command)->toContain('--parallel')
|
||||
->and($command)->toContain('--testsuite=Unit,Feature')
|
||||
->and(implode(' ', $command))->toContain('--exclude-group=browser,heavy-governance');
|
||||
->and($command)->toContain('--configuration='.$configurationPath)
|
||||
->and($command)->toContain('--testsuite=Lane')
|
||||
->and($configurationContents)->toContain('tests/Feature/Auth/BreakGlassWorkspaceOwnerRecoveryTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Findings/FindingBulkActionsTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Guards/ActionSurfaceContractTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php');
|
||||
});
|
||||
|
||||
it('keeps fast-feedback narrower than the broader confidence lane', function (): void {
|
||||
it('keeps fast-feedback narrower than confidence and rejects broad surface or discovery families', function (): void {
|
||||
$fastTargets = TestLaneManifest::discoverFiles('fast-feedback');
|
||||
$confidenceTargets = TestLaneManifest::discoverFiles('confidence');
|
||||
$validation = TestLaneManifest::validateLanePlacement(
|
||||
laneId: 'fast-feedback',
|
||||
filePath: 'tests/Feature/Guards/ActionSurfaceContractTest.php',
|
||||
);
|
||||
|
||||
expect($fastTargets)->not->toBeEmpty()
|
||||
->and(count($fastTargets))->toBeLessThan(count($confidenceTargets));
|
||||
->and(count($fastTargets))->toBeLessThan(count($confidenceTargets))
|
||||
->and($fastTargets)->not->toContain('tests/Feature/Guards/ActionSurfaceContractTest.php')
|
||||
->and($validation['valid'])->toBeFalse()
|
||||
->and($validation['resolvedClassificationId'])->toBe('surface-guard');
|
||||
});
|
||||
@ -5,19 +5,31 @@
|
||||
use Illuminate\Support\Collection;
|
||||
use Tests\Support\TestLaneManifest;
|
||||
|
||||
it('excludes browser and initial heavy-governance families from fast-feedback discovery', function (): void {
|
||||
it('excludes browser, discovery-heavy, and surface-guard families from fast-feedback discovery', function (): void {
|
||||
$files = collect(TestLaneManifest::discoverFiles('fast-feedback'));
|
||||
$discoveryValidation = TestLaneManifest::validateLanePlacement(
|
||||
laneId: 'fast-feedback',
|
||||
filePath: 'tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php',
|
||||
);
|
||||
|
||||
expect($files->contains(static fn (string $path): bool => str_starts_with($path, 'tests/Browser/')))->toBeFalse()
|
||||
->and($files->contains(static fn (string $path): bool => str_starts_with($path, 'tests/Feature/OpsUx/')))->toBeFalse()
|
||||
->and($files)->not->toContain('tests/Feature/Guards/ActionSurfaceContractTest.php')
|
||||
->and($files->contains(static fn (string $path): bool => str_starts_with($path, 'tests/Architecture/')))->toBeFalse()
|
||||
->and($files->contains(static fn (string $path): bool => str_starts_with($path, 'tests/Deprecation/')))->toBeFalse();
|
||||
->and($files->contains(static fn (string $path): bool => str_starts_with($path, 'tests/Deprecation/')))->toBeFalse()
|
||||
->and($discoveryValidation['valid'])->toBeFalse()
|
||||
->and($discoveryValidation['resolvedClassificationId'])->toBe('discovery-heavy');
|
||||
});
|
||||
|
||||
it('keeps fast-feedback focused on the quick-edit families the manifest declares', function (): void {
|
||||
$files = new Collection(TestLaneManifest::discoverFiles('fast-feedback'));
|
||||
|
||||
expect($files->contains(static fn (string $path): bool => str_starts_with($path, 'tests/Unit/')))->toBeTrue()
|
||||
->and($files->contains(static fn (string $path): bool => str_starts_with($path, 'tests/Feature/Guards/')))->toBeTrue();
|
||||
->and($files->contains(static fn (string $path): bool => str_starts_with($path, 'tests/Feature/Guards/')))->toBeTrue()
|
||||
->and($files)->not->toContain(
|
||||
'tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php',
|
||||
'tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php',
|
||||
'tests/Feature/Findings/FindingBulkActionsTest.php',
|
||||
'tests/Feature/Rbac/OnboardingWizardUiEnforcementTest.php',
|
||||
);
|
||||
});
|
||||
@ -52,3 +52,48 @@
|
||||
->and(data_get($stable, 'sharedFixtureSlimmingComparison.status'))->toBe('stable')
|
||||
->and(data_get($regressed, 'sharedFixtureSlimmingComparison.status'))->toBe('regressed');
|
||||
});
|
||||
|
||||
it('defines lane, classification, and family budget targets for heavy-governance attribution', function (): void {
|
||||
$budgetTargets = collect(TestLaneManifest::budgetTargets());
|
||||
|
||||
expect($budgetTargets->contains(static fn (array $target): bool => $target['targetType'] === 'lane' && $target['targetId'] === 'heavy-governance'))->toBeTrue()
|
||||
->and($budgetTargets->contains(static fn (array $target): bool => $target['targetType'] === 'classification' && $target['targetId'] === 'surface-guard'))->toBeTrue()
|
||||
->and($budgetTargets->contains(static fn (array $target): bool => $target['targetType'] === 'classification' && $target['targetId'] === 'discovery-heavy'))->toBeTrue()
|
||||
->and($budgetTargets->contains(static fn (array $target): bool => $target['targetType'] === 'family' && $target['targetId'] === 'action-surface-contract'))->toBeTrue()
|
||||
->and($budgetTargets->contains(static fn (array $target): bool => $target['targetType'] === 'family' && $target['targetId'] === 'ops-ux-governance'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('evaluates heavy-governance budgets against named class and family totals', function (): void {
|
||||
$durationsByFile = [
|
||||
'tests/Feature/Guards/ActionSurfaceContractTest.php' => 31.2,
|
||||
'tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php' => 17.4,
|
||||
'tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php' => 16.1,
|
||||
'tests/Feature/Filament/Alerts/AlertsKpiHeaderTest.php' => 9.8,
|
||||
'tests/Feature/Guards/OperationLifecycleOpsUxGuardTest.php' => 8.7,
|
||||
];
|
||||
|
||||
$slowestEntries = collect($durationsByFile)
|
||||
->map(static fn (float $seconds, string $file): array => [
|
||||
'label' => $file.'::synthetic',
|
||||
'subject' => $file.'::synthetic',
|
||||
'filePath' => $file,
|
||||
'durationSeconds' => $seconds,
|
||||
'wallClockSeconds' => $seconds,
|
||||
'laneId' => 'heavy-governance',
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$report = TestLaneReport::buildReport(
|
||||
laneId: 'heavy-governance',
|
||||
wallClockSeconds: 110.0,
|
||||
slowestEntries: $slowestEntries,
|
||||
durationsByFile: $durationsByFile,
|
||||
);
|
||||
|
||||
expect(collect($report['budgetEvaluations'])->pluck('targetType')->unique()->values()->all())
|
||||
->toEqualCanonicalizing(['lane', 'classification', 'family'])
|
||||
->and(collect($report['budgetEvaluations'])->contains(static fn (array $evaluation): bool => $evaluation['targetType'] === 'lane' && $evaluation['targetId'] === 'heavy-governance'))->toBeTrue()
|
||||
->and(collect($report['budgetEvaluations'])->contains(static fn (array $evaluation): bool => $evaluation['targetType'] === 'classification' && $evaluation['targetId'] === 'surface-guard'))->toBeTrue()
|
||||
->and(collect($report['budgetEvaluations'])->contains(static fn (array $evaluation): bool => $evaluation['targetType'] === 'family' && $evaluation['targetId'] === 'action-surface-contract'))->toBeTrue();
|
||||
});
|
||||
@ -5,21 +5,62 @@
|
||||
use Illuminate\Support\Collection;
|
||||
use Tests\Support\TestLaneManifest;
|
||||
|
||||
it('routes the initial architecture, deprecation, ops-ux, and action-surface batch into heavy-governance', function (): void {
|
||||
it('routes escalated workflow, discovery-heavy, and broad surface-guard families into heavy-governance', function (): void {
|
||||
$lane = TestLaneManifest::lane('heavy-governance');
|
||||
$files = new Collection(TestLaneManifest::discoverFiles('heavy-governance'));
|
||||
|
||||
expect($lane['includedFamilies'])->toContain('architecture-governance', 'ops-ux')
|
||||
->and($lane['selectors']['includeGroups'])->toContain('heavy-governance')
|
||||
->and($files)->toContain('tests/Architecture/PlatformVocabularyBoundaryGuardTest.php')
|
||||
->and($files)->toContain('tests/Architecture/ReasonTranslationPrimarySurfaceGuardTest.php')
|
||||
->and($files)->toContain('tests/Deprecation/IsPlatformSuperadminDeprecationTest.php')
|
||||
->and($files)->toContain('tests/Feature/Guards/ActionSurfaceContractTest.php');
|
||||
expect($lane['includedFamilies'])->toContain('ui-workflow', 'surface-guard', 'discovery-heavy')
|
||||
->and($files)->toContain(
|
||||
'tests/Architecture/PlatformVocabularyBoundaryGuardTest.php',
|
||||
'tests/Architecture/ReasonTranslationPrimarySurfaceGuardTest.php',
|
||||
'tests/Deprecation/IsPlatformSuperadminDeprecationTest.php',
|
||||
'tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php',
|
||||
'tests/Feature/Filament/BaselineActionAuthorizationTest.php',
|
||||
'tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php',
|
||||
'tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php',
|
||||
'tests/Feature/Findings/FindingBulkActionsTest.php',
|
||||
'tests/Feature/Findings/FindingExceptionRenewalTest.php',
|
||||
'tests/Feature/Findings/FindingsListFiltersTest.php',
|
||||
'tests/Feature/Findings/FindingWorkflowRowActionsTest.php',
|
||||
'tests/Feature/Findings/FindingWorkflowViewActionsTest.php',
|
||||
'tests/Feature/Filament/WorkspaceOnlySurfaceTenantIndependenceTest.php',
|
||||
'tests/Feature/Guards/ActionSurfaceContractTest.php',
|
||||
'tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php',
|
||||
'tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php',
|
||||
'tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php',
|
||||
'tests/Feature/Rbac/WorkspaceMembershipsRelationManagerUiEnforcementTest.php',
|
||||
'tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php',
|
||||
'tests/Feature/Filament/TenantReviewHeaderDisciplineTest.php',
|
||||
'tests/Feature/Filament/PanelNavigationSegregationTest.php',
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the heavy-governance command group-driven for intentionally expensive families', function (): void {
|
||||
it('keeps the heavy-governance command config-driven and free of retained confidence workflows', function (): void {
|
||||
$command = TestLaneManifest::buildCommand('heavy-governance');
|
||||
$files = TestLaneManifest::discoverFiles('heavy-governance');
|
||||
$configurationPath = TestLaneManifest::laneConfigurationPath('heavy-governance');
|
||||
$configurationContents = (string) file_get_contents(TestLaneManifest::absolutePath($configurationPath));
|
||||
|
||||
expect($command)->toContain('--group=heavy-governance')
|
||||
->and(TestLaneManifest::commandRef('heavy-governance'))->toBe('test:heavy');
|
||||
expect(TestLaneManifest::commandRef('heavy-governance'))->toBe('test:heavy')
|
||||
->and($command)->toContain('--configuration='.$configurationPath)
|
||||
->and($command)->toContain('--testsuite=Lane')
|
||||
->and($configurationContents)->toContain('tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/Filament/BaselineActionAuthorizationTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/Findings/FindingBulkActionsTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/Findings/FindingExceptionRenewalTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/Findings/FindingsListFiltersTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/Findings/FindingWorkflowRowActionsTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/Findings/FindingWorkflowViewActionsTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/Filament/WorkspaceOnlySurfaceTenantIndependenceTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/Guards/ActionSurfaceContractTest.php')
|
||||
->and($configurationContents)->toContain('tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php')
|
||||
->and($files)->not->toContain(
|
||||
'tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php',
|
||||
'tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php',
|
||||
'tests/Feature/Rbac/OnboardingWizardUiEnforcementTest.php',
|
||||
'tests/Feature/Filament/BackupSetAdminTenantParityTest.php',
|
||||
);
|
||||
});
|
||||
@ -3,15 +3,61 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use Tests\Support\TestLaneManifest;
|
||||
use Tests\Support\TestLaneReport;
|
||||
|
||||
it('keeps profiling serial and artifact-rich for slow-test drift analysis', function (): void {
|
||||
$lane = TestLaneManifest::lane('profiling');
|
||||
$command = TestLaneManifest::buildCommand('profiling');
|
||||
$configurationPath = TestLaneManifest::laneConfigurationPath('profiling');
|
||||
$configurationContents = (string) file_get_contents(TestLaneManifest::absolutePath($configurationPath));
|
||||
|
||||
expect($lane['governanceClass'])->toBe('support')
|
||||
->and($lane['parallelMode'])->toBe('forbidden')
|
||||
->and($lane['artifacts'])->toContain('profile-top', 'junit-xml', 'summary', 'budget-report')
|
||||
->and($command)->toContain('--profile')
|
||||
->and($command)->not->toContain('--parallel')
|
||||
->and(implode(' ', $command))->toContain('--exclude-group=browser');
|
||||
->and($command)->toContain('--configuration='.$configurationPath)
|
||||
->and($configurationContents)->toContain('tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php')
|
||||
->and($configurationContents)->not->toContain('tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php');
|
||||
});
|
||||
|
||||
it('builds top 10 attribution-rich profiling reports for mixed workflow and governance files', function (): void {
|
||||
$durationsByFile = [
|
||||
'tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php' => 22.4,
|
||||
'tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php' => 20.1,
|
||||
'tests/Feature/Rbac/OnboardingWizardUiEnforcementTest.php' => 18.5,
|
||||
'tests/Feature/Guards/ActionSurfaceContractTest.php' => 17.3,
|
||||
'tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php' => 16.2,
|
||||
'tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php' => 15.1,
|
||||
'tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php' => 13.7,
|
||||
'tests/Feature/Rbac/WorkspaceMembershipsRelationManagerUiEnforcementTest.php' => 12.8,
|
||||
'tests/Feature/Filament/TenantReviewHeaderDisciplineTest.php' => 11.4,
|
||||
'tests/Feature/Filament/BackupSetAdminTenantParityTest.php' => 9.6,
|
||||
];
|
||||
|
||||
$slowestEntries = collect($durationsByFile)
|
||||
->map(static fn (float $seconds, string $file): array => [
|
||||
'label' => $file.'::synthetic',
|
||||
'subject' => $file.'::synthetic',
|
||||
'filePath' => $file,
|
||||
'durationSeconds' => $seconds,
|
||||
'wallClockSeconds' => $seconds,
|
||||
'laneId' => 'profiling',
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$report = TestLaneReport::buildReport(
|
||||
laneId: 'profiling',
|
||||
wallClockSeconds: 181.7,
|
||||
slowestEntries: $slowestEntries,
|
||||
durationsByFile: $durationsByFile,
|
||||
);
|
||||
|
||||
expect($report['slowestEntries'])->toHaveCount(10)
|
||||
->and($report['slowestEntries'][0]['wallClockSeconds'])->toBeGreaterThanOrEqual($report['slowestEntries'][1]['wallClockSeconds'])
|
||||
->and(collect($report['classificationAttribution'])->pluck('classificationId')->all())
|
||||
->toContain('ui-light', 'ui-workflow', 'surface-guard', 'discovery-heavy')
|
||||
->and(collect($report['familyAttribution'])->pluck('familyId')->all())
|
||||
->toContain('baseline-compare-matrix-workflow', 'action-surface-contract', 'backup-set-admin-tenant-parity');
|
||||
});
|
||||
@ -4,6 +4,22 @@
|
||||
|
||||
use Tests\Support\TestLaneReport;
|
||||
|
||||
function heavyGovernanceSyntheticHotspots(): array
|
||||
{
|
||||
return [
|
||||
['file' => 'tests/Feature/Guards/ActionSurfaceContractTest.php', 'seconds' => 31.2],
|
||||
['file' => 'tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php', 'seconds' => 17.4],
|
||||
['file' => 'tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php', 'seconds' => 16.1],
|
||||
['file' => 'tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php', 'seconds' => 14.5],
|
||||
['file' => 'tests/Feature/Rbac/WorkspaceMembershipsRelationManagerUiEnforcementTest.php', 'seconds' => 12.8],
|
||||
['file' => 'tests/Feature/Filament/TenantReviewHeaderDisciplineTest.php', 'seconds' => 11.7],
|
||||
['file' => 'tests/Feature/Filament/PanelNavigationSegregationTest.php', 'seconds' => 10.9],
|
||||
['file' => 'tests/Feature/Filament/Alerts/AlertsKpiHeaderTest.php', 'seconds' => 9.8],
|
||||
['file' => 'tests/Feature/Guards/OperationLifecycleOpsUxGuardTest.php', 'seconds' => 8.7],
|
||||
['file' => 'tests/Feature/ProviderConnections/CredentialLeakGuardTest.php', 'seconds' => 7.6],
|
||||
];
|
||||
}
|
||||
|
||||
it('keeps lane artifact paths app-root relative under storage/logs/test-lanes', function (): void {
|
||||
$artifacts = TestLaneReport::artifactPaths('fast-feedback');
|
||||
|
||||
@ -22,6 +38,41 @@
|
||||
->and((string) file_get_contents($gitignore))->toContain('!.gitignore');
|
||||
});
|
||||
|
||||
it('publishes heavy attribution and budget payloads under the canonical artifact root', function (): void {
|
||||
$durationsByFile = collect(heavyGovernanceSyntheticHotspots())
|
||||
->mapWithKeys(static fn (array $entry): array => [$entry['file'] => $entry['seconds']])
|
||||
->all();
|
||||
|
||||
$slowestEntries = collect(heavyGovernanceSyntheticHotspots())
|
||||
->map(static fn (array $entry): array => [
|
||||
'label' => $entry['file'].'::synthetic',
|
||||
'subject' => $entry['file'].'::synthetic',
|
||||
'filePath' => $entry['file'],
|
||||
'durationSeconds' => $entry['seconds'],
|
||||
'wallClockSeconds' => $entry['seconds'],
|
||||
'laneId' => 'heavy-governance',
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$report = TestLaneReport::buildReport(
|
||||
laneId: 'heavy-governance',
|
||||
wallClockSeconds: 118.4,
|
||||
slowestEntries: $slowestEntries,
|
||||
durationsByFile: $durationsByFile,
|
||||
);
|
||||
|
||||
expect($report['artifactDirectory'])->toBe('storage/logs/test-lanes')
|
||||
->and($report['slowestEntries'])->toHaveCount(10)
|
||||
->and(collect($report['classificationAttribution'])->pluck('classificationId')->all())
|
||||
->toContain('surface-guard', 'discovery-heavy')
|
||||
->and(collect($report['familyAttribution'])->pluck('familyId')->all())
|
||||
->toContain('action-surface-contract', 'policy-resource-admin-search-parity', 'ops-ux-governance')
|
||||
->and(collect($report['budgetEvaluations'])->pluck('targetType')->unique()->values()->all())
|
||||
->toEqualCanonicalizing(['lane', 'classification', 'family'])
|
||||
->and($report['familyBudgetEvaluations'])->not->toBeEmpty();
|
||||
});
|
||||
|
||||
it('publishes the shared fixture slimming comparison only for the governed standard lanes', function (): void {
|
||||
$fastFeedback = TestLaneReport::buildReport(
|
||||
laneId: 'fast-feedback',
|
||||
|
||||
@ -16,6 +16,11 @@
|
||||
'test:heavy',
|
||||
'test:profile',
|
||||
'test:junit',
|
||||
'test:report',
|
||||
'test:report:confidence',
|
||||
'test:report:browser',
|
||||
'test:report:heavy',
|
||||
'test:report:profile',
|
||||
'sail:test',
|
||||
])
|
||||
->and(TestLaneManifest::commandRef('fast-feedback'))->toBe('test')
|
||||
@ -32,12 +37,36 @@
|
||||
});
|
||||
|
||||
it('routes the foundational lane commands through stable artisan arguments', function (): void {
|
||||
$fastFeedbackConfig = TestLaneManifest::laneConfigurationPath('fast-feedback');
|
||||
$fastFeedbackContents = (string) file_get_contents(TestLaneManifest::absolutePath($fastFeedbackConfig));
|
||||
$confidenceConfig = TestLaneManifest::laneConfigurationPath('confidence');
|
||||
$confidenceContents = (string) file_get_contents(TestLaneManifest::absolutePath($confidenceConfig));
|
||||
$browserConfig = TestLaneManifest::laneConfigurationPath('browser');
|
||||
$browserContents = (string) file_get_contents(TestLaneManifest::absolutePath($browserConfig));
|
||||
$heavyConfig = TestLaneManifest::laneConfigurationPath('heavy-governance');
|
||||
$heavyContents = (string) file_get_contents(TestLaneManifest::absolutePath($heavyConfig));
|
||||
|
||||
expect(TestLaneManifest::buildCommand('fast-feedback'))->toContain('--parallel')
|
||||
->and(TestLaneManifest::buildCommand('fast-feedback'))->toContain('--group=fast-feedback')
|
||||
->and(TestLaneManifest::buildCommand('fast-feedback'))->toContain('--testsuite=Unit,Feature')
|
||||
->and(TestLaneManifest::buildCommand('confidence'))->toContain('--testsuite=Unit,Feature')
|
||||
->and(TestLaneManifest::buildCommand('browser'))->toContain('--group=browser')
|
||||
->and(TestLaneManifest::buildCommand('browser'))->toContain('--testsuite=Browser')
|
||||
->and(TestLaneManifest::buildCommand('heavy-governance'))->toContain('--group=heavy-governance')
|
||||
->and(TestLaneManifest::buildCommand('fast-feedback'))->toContain('--configuration='.$fastFeedbackConfig)
|
||||
->and($fastFeedbackContents)->not->toContain('tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php')
|
||||
->and($fastFeedbackContents)->not->toContain('tests/Feature/Findings/FindingBulkActionsTest.php')
|
||||
->and($fastFeedbackContents)->not->toContain('tests/Feature/Guards/ActionSurfaceContractTest.php')
|
||||
->and(TestLaneManifest::buildCommand('confidence'))->toContain('--configuration='.$confidenceConfig)
|
||||
->and($confidenceContents)->toContain('tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php')
|
||||
->and($confidenceContents)->not->toContain('tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php')
|
||||
->and($confidenceContents)->not->toContain('tests/Feature/Filament/BaselineActionAuthorizationTest.php')
|
||||
->and($confidenceContents)->not->toContain('tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php')
|
||||
->and($confidenceContents)->not->toContain('tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php')
|
||||
->and($confidenceContents)->not->toContain('tests/Feature/Findings/FindingBulkActionsTest.php')
|
||||
->and($confidenceContents)->not->toContain('tests/Feature/Guards/ActionSurfaceContractTest.php')
|
||||
->and(TestLaneManifest::buildCommand('browser'))->toContain('--configuration='.$browserConfig)
|
||||
->and($browserContents)->toContain('tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php')
|
||||
->and(TestLaneManifest::buildCommand('heavy-governance'))->toContain('--configuration='.$heavyConfig)
|
||||
->and($heavyContents)->toContain('tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php')
|
||||
->and($heavyContents)->toContain('tests/Feature/Filament/BaselineActionAuthorizationTest.php')
|
||||
->and($heavyContents)->toContain('tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php')
|
||||
->and($heavyContents)->toContain('tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php')
|
||||
->and($heavyContents)->toContain('tests/Feature/Findings/FindingBulkActionsTest.php')
|
||||
->and($heavyContents)->toContain('tests/Feature/Guards/ActionSurfaceContractTest.php')
|
||||
->and(TestLaneManifest::buildCommand('junit'))->toContain('--parallel');
|
||||
});
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
use Tests\Support\TestLaneManifest;
|
||||
|
||||
it('declares the six checked-in lanes with a single fast-feedback default', function (): void {
|
||||
it('declares the six checked-in lanes with a single fast-feedback default and the spec 208 metadata surfaces', function (): void {
|
||||
$manifest = TestLaneManifest::manifest();
|
||||
$laneIds = array_column($manifest['lanes'], 'id');
|
||||
$defaultLanes = array_values(array_filter(
|
||||
@ -14,6 +14,16 @@
|
||||
|
||||
expect($manifest['version'])->toBe(1)
|
||||
->and($manifest['artifactDirectory'])->toBe('storage/logs/test-lanes')
|
||||
->and($manifest)->toHaveKeys([
|
||||
'classifications',
|
||||
'families',
|
||||
'mixedFileResolutions',
|
||||
'placementRules',
|
||||
'driftGuards',
|
||||
'budgetTargets',
|
||||
'lanes',
|
||||
'familyBudgets',
|
||||
])
|
||||
->and($laneIds)->toEqualCanonicalizing([
|
||||
'fast-feedback',
|
||||
'confidence',
|
||||
@ -26,7 +36,7 @@
|
||||
->and($defaultLanes[0]['id'])->toBe('fast-feedback');
|
||||
});
|
||||
|
||||
it('keeps every lane declaration populated with governance metadata, selectors, and budgets', function (): void {
|
||||
it('keeps every lane declaration populated with governance metadata, selectors, and segmented family expectations', function (): void {
|
||||
foreach (TestLaneManifest::manifest()['lanes'] as $lane) {
|
||||
expect(trim($lane['description']))->not->toBe('')
|
||||
->and(trim($lane['intendedAudience']))->not->toBe('')
|
||||
@ -52,13 +62,55 @@
|
||||
expect($selectors)->toHaveKey($selectorKey);
|
||||
}
|
||||
}
|
||||
|
||||
expect(TestLaneManifest::lane('confidence')['includedFamilies'])->toContain('ui-light', 'ui-workflow')
|
||||
->and(TestLaneManifest::lane('confidence')['excludedFamilies'])->toContain('surface-guard', 'discovery-heavy')
|
||||
->and(TestLaneManifest::lane('heavy-governance')['includedFamilies'])->toContain('surface-guard', 'discovery-heavy');
|
||||
});
|
||||
|
||||
it('seeds at least one initial heavy family budget beside the lane-level budgets', function (): void {
|
||||
it('exposes the spec 208 classification catalog and seeded family inventory with required metadata', function (): void {
|
||||
$classifications = collect(TestLaneManifest::classifications())->keyBy('classificationId');
|
||||
$families = collect(TestLaneManifest::families())->keyBy('familyId');
|
||||
|
||||
expect($classifications->keys()->all())->toEqualCanonicalizing([
|
||||
'ui-light',
|
||||
'ui-workflow',
|
||||
'surface-guard',
|
||||
'discovery-heavy',
|
||||
'browser',
|
||||
])
|
||||
->and($classifications->get('browser')['allowedLaneIds'])->toBe(['browser'])
|
||||
->and($classifications->get('surface-guard')['defaultLaneId'])->toBe('heavy-governance')
|
||||
->and($classifications->get('discovery-heavy')['forbiddenLaneIds'])->toContain('fast-feedback', 'confidence')
|
||||
->and($families->has('baseline-profile-start-surfaces'))->toBeTrue()
|
||||
->and($families->has('findings-workflow-surfaces'))->toBeTrue()
|
||||
->and($families->has('finding-bulk-actions-workflow'))->toBeTrue()
|
||||
->and($families->has('drift-bulk-triage-all-matching'))->toBeTrue()
|
||||
->and($families->has('policy-resource-admin-search-parity'))->toBeTrue()
|
||||
->and($families->has('workspace-only-admin-surface-independence'))->toBeTrue()
|
||||
->and($families->has('workspace-settings-slice-management'))->toBeTrue()
|
||||
->and($families->has('baseline-compare-matrix-workflow'))->toBeTrue()
|
||||
->and($families->has('browser-smoke'))->toBeTrue();
|
||||
|
||||
foreach (TestLaneManifest::families() as $family) {
|
||||
expect(trim($family['purpose']))->not->toBe('')
|
||||
->and(trim($family['currentLaneId']))->not->toBe('')
|
||||
->and(trim($family['targetLaneId']))->not->toBe('')
|
||||
->and($family['selectors'])->not->toBeEmpty()
|
||||
->and($family['hotspotFiles'])->not->toBeEmpty()
|
||||
->and(trim($family['validationStatus']))->not->toBe('');
|
||||
|
||||
if ($family['targetLaneId'] === 'confidence') {
|
||||
expect(trim((string) ($family['confidenceRationale'] ?? '')))->not->toBe('');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps family budgets derived from the generic budget targets for report consumers', function (): void {
|
||||
$familyBudgets = TestLaneManifest::familyBudgets();
|
||||
|
||||
expect($familyBudgets)->not->toBeEmpty()
|
||||
->and($familyBudgets[0]['familyId'])->toBeString()
|
||||
->and($familyBudgets[0]['selectors'])->not->toBeEmpty()
|
||||
->and($familyBudgets[0]['thresholdSeconds'])->toBeGreaterThan(0);
|
||||
->and($familyBudgets[0])->toHaveKeys(['familyId', 'targetType', 'targetId', 'selectors', 'thresholdSeconds'])
|
||||
->and(collect($familyBudgets)->pluck('familyId')->all())
|
||||
->toContain('action-surface-contract', 'browser-smoke', 'baseline-compare-matrix-workflow', 'baseline-profile-start-surfaces', 'drift-bulk-triage-all-matching', 'finding-bulk-actions-workflow', 'findings-workflow-surfaces', 'workspace-only-admin-surface-independence', 'workspace-settings-slice-management');
|
||||
});
|
||||
@ -4,16 +4,79 @@
|
||||
|
||||
use Tests\Support\TestLaneManifest;
|
||||
|
||||
it('keeps the first audited heavy batch out of the confidence lane and inside heavy-governance', function (): void {
|
||||
it('declares the spec 208 five-class catalog with the expected lane semantics', function (): void {
|
||||
$classifications = collect(TestLaneManifest::classifications())->keyBy('classificationId');
|
||||
|
||||
expect($classifications->keys()->all())->toEqualCanonicalizing([
|
||||
'ui-light',
|
||||
'ui-workflow',
|
||||
'surface-guard',
|
||||
'discovery-heavy',
|
||||
'browser',
|
||||
])
|
||||
->and($classifications->get('ui-light')['allowedLaneIds'])->toContain('fast-feedback', 'confidence')
|
||||
->and($classifications->get('ui-workflow')['defaultLaneId'])->toBe('confidence')
|
||||
->and($classifications->get('surface-guard')['forbiddenLaneIds'])->toContain('fast-feedback', 'confidence')
|
||||
->and($classifications->get('discovery-heavy')['defaultLaneId'])->toBe('heavy-governance')
|
||||
->and($classifications->get('browser')['allowedLaneIds'])->toBe(['browser']);
|
||||
});
|
||||
|
||||
it('keeps the seeded heavy batch out of confidence and inside heavy-governance', function (): void {
|
||||
$confidenceFiles = TestLaneManifest::discoverFiles('confidence');
|
||||
$heavyFiles = TestLaneManifest::discoverFiles('heavy-governance');
|
||||
|
||||
expect($confidenceFiles)->not->toContain('tests/Architecture/PlatformVocabularyBoundaryGuardTest.php')
|
||||
->and($confidenceFiles)->not->toContain('tests/Architecture/ReasonTranslationPrimarySurfaceGuardTest.php')
|
||||
->and($confidenceFiles)->not->toContain('tests/Deprecation/IsPlatformSuperadminDeprecationTest.php')
|
||||
->and($confidenceFiles)->not->toContain('tests/Feature/Guards/ActionSurfaceContractTest.php')
|
||||
->and($heavyFiles)->toContain('tests/Architecture/PlatformVocabularyBoundaryGuardTest.php')
|
||||
->and($heavyFiles)->toContain('tests/Architecture/ReasonTranslationPrimarySurfaceGuardTest.php')
|
||||
->and($heavyFiles)->toContain('tests/Deprecation/IsPlatformSuperadminDeprecationTest.php')
|
||||
->and($heavyFiles)->toContain('tests/Feature/Guards/ActionSurfaceContractTest.php');
|
||||
expect($confidenceFiles)->not->toContain(
|
||||
'tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php',
|
||||
'tests/Feature/Filament/BaselineActionAuthorizationTest.php',
|
||||
'tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php',
|
||||
'tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php',
|
||||
'tests/Feature/Findings/FindingBulkActionsTest.php',
|
||||
'tests/Feature/Findings/FindingExceptionRenewalTest.php',
|
||||
'tests/Feature/Findings/FindingsListFiltersTest.php',
|
||||
'tests/Feature/Findings/FindingWorkflowRowActionsTest.php',
|
||||
'tests/Feature/Findings/FindingWorkflowViewActionsTest.php',
|
||||
'tests/Feature/Filament/WorkspaceOnlySurfaceTenantIndependenceTest.php',
|
||||
'tests/Feature/Guards/ActionSurfaceContractTest.php',
|
||||
'tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php',
|
||||
'tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php',
|
||||
'tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php',
|
||||
'tests/Feature/Rbac/WorkspaceMembershipsRelationManagerUiEnforcementTest.php',
|
||||
'tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php',
|
||||
'tests/Feature/Filament/TenantReviewHeaderDisciplineTest.php',
|
||||
'tests/Feature/Filament/PanelNavigationSegregationTest.php',
|
||||
)
|
||||
->and($heavyFiles)->toContain(
|
||||
'tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php',
|
||||
'tests/Feature/Filament/BaselineActionAuthorizationTest.php',
|
||||
'tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php',
|
||||
'tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php',
|
||||
'tests/Feature/Findings/FindingBulkActionsTest.php',
|
||||
'tests/Feature/Findings/FindingExceptionRenewalTest.php',
|
||||
'tests/Feature/Findings/FindingsListFiltersTest.php',
|
||||
'tests/Feature/Findings/FindingWorkflowRowActionsTest.php',
|
||||
'tests/Feature/Findings/FindingWorkflowViewActionsTest.php',
|
||||
'tests/Feature/Filament/WorkspaceOnlySurfaceTenantIndependenceTest.php',
|
||||
'tests/Feature/Guards/ActionSurfaceContractTest.php',
|
||||
'tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php',
|
||||
'tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php',
|
||||
'tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php',
|
||||
'tests/Feature/Rbac/WorkspaceMembershipsRelationManagerUiEnforcementTest.php',
|
||||
'tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php',
|
||||
'tests/Feature/Filament/TenantReviewHeaderDisciplineTest.php',
|
||||
'tests/Feature/Filament/PanelNavigationSegregationTest.php',
|
||||
);
|
||||
});
|
||||
|
||||
it('describes mixed-file and wrong-lane placement with actionable output', function (): void {
|
||||
$validation = TestLaneManifest::validateLanePlacement(
|
||||
laneId: 'confidence',
|
||||
filePath: 'tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php',
|
||||
);
|
||||
|
||||
expect($validation['valid'])->toBeFalse()
|
||||
->and($validation['resolvedClassificationId'])->toBe('discovery-heavy')
|
||||
->and(implode(' ', $validation['reasons']))->toContain('policy-resource-admin-search-parity');
|
||||
|
||||
expect(TestLaneManifest::describeFilePlacement('tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php'))
|
||||
->toContain('baseline-compare-matrix-workflow', 'ui-workflow', 'Mixed-file');
|
||||
});
|
||||
@ -78,36 +78,46 @@
|
||||
pest()->group('browser')
|
||||
->in('Browser');
|
||||
|
||||
pest()->group('fast-feedback')
|
||||
pest()->group('ui-light')
|
||||
->in(
|
||||
'Unit',
|
||||
'Feature/Auth',
|
||||
'Feature/Authorization',
|
||||
'Feature/EntraAdminRoles',
|
||||
'Feature/Findings',
|
||||
'Feature/Guards',
|
||||
'Feature/Monitoring',
|
||||
'Feature/Navigation',
|
||||
'Feature/Onboarding',
|
||||
'Feature/RequiredPermissions',
|
||||
'Feature/Tenants',
|
||||
'Feature/Workspaces',
|
||||
'Feature/AdminConsentCallbackTest.php',
|
||||
'Feature/AdminNewRedirectTest.php',
|
||||
'Feature/Filament/BackupSetAdminTenantParityTest.php',
|
||||
);
|
||||
|
||||
pest()->group('heavy-governance')
|
||||
pest()->group('ui-workflow')
|
||||
->in(
|
||||
'Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php',
|
||||
'Feature/Baselines/BaselineCompareMatrixBuilderTest.php',
|
||||
'Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php',
|
||||
'Feature/Findings/FindingExceptionRenewalTest.php',
|
||||
'Feature/Findings/FindingsListFiltersTest.php',
|
||||
'Feature/Findings/FindingWorkflowRowActionsTest.php',
|
||||
'Feature/Findings/FindingWorkflowViewActionsTest.php',
|
||||
'Feature/Filament/BaselineActionAuthorizationTest.php',
|
||||
'Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php',
|
||||
'Feature/Filament/BaselineProfileCompareStartSurfaceTest.php',
|
||||
'Feature/Findings/FindingBulkActionsTest.php',
|
||||
'Feature/SettingsFoundation/WorkspaceSettingsManageTest.php',
|
||||
'Feature/Rbac/OnboardingWizardUiEnforcementTest.php',
|
||||
);
|
||||
|
||||
pest()->group('surface-guard')
|
||||
->in(
|
||||
'Architecture',
|
||||
'Deprecation',
|
||||
'Feature/078',
|
||||
'Feature/090',
|
||||
'Feature/144',
|
||||
'Feature/OpsUx',
|
||||
'Feature/Filament/Alerts/AlertsKpiHeaderTest.php',
|
||||
'Feature/Filament/PanelNavigationSegregationTest.php',
|
||||
'Feature/Filament/TenantReviewHeaderDisciplineTest.php',
|
||||
'Feature/Filament/WorkspaceOnlySurfaceTenantIndependenceTest.php',
|
||||
'Feature/Guards/ActionSurfaceContractTest.php',
|
||||
'Feature/Guards/OperationLifecycleOpsUxGuardTest.php',
|
||||
'Feature/ProviderConnections/CredentialLeakGuardTest.php',
|
||||
'Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php',
|
||||
'Feature/Rbac/WorkspaceMembershipsRelationManagerUiEnforcementTest.php',
|
||||
);
|
||||
|
||||
pest()->group('discovery-heavy')
|
||||
->in(
|
||||
'Feature/Filament/PolicyResourceAdminSearchParityTest.php',
|
||||
'Feature/Filament/PolicyVersionAdminSearchParityTest.php',
|
||||
);
|
||||
|
||||
beforeEach(function () {
|
||||
|
||||
@ -47,7 +47,9 @@ public function evaluate(float $measuredSeconds): array
|
||||
$budgetStatus = 'within-budget';
|
||||
|
||||
if ($measuredSeconds > $this->thresholdSeconds) {
|
||||
$budgetStatus = $this->enforcement === 'warn' ? 'warning' : 'over-budget';
|
||||
$budgetStatus = in_array($this->enforcement, ['report-only', 'warn'], true)
|
||||
? 'warning'
|
||||
: 'over-budget';
|
||||
}
|
||||
|
||||
return array_filter([
|
||||
@ -58,59 +60,63 @@ public function evaluate(float $measuredSeconds): array
|
||||
'baselineDeltaTargetPercent' => $this->baselineDeltaTargetPercent,
|
||||
'measuredSeconds' => round($measuredSeconds, 6),
|
||||
'budgetStatus' => $budgetStatus,
|
||||
'notes' => $this->notes,
|
||||
'reviewCadence' => $this->reviewCadence,
|
||||
], static fn (mixed $value): bool => $value !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $familyBudgets
|
||||
* @param array<string, float|int> $durationsByFile
|
||||
* @return list<array<string, int|float|string|array<int, string>>>
|
||||
* @param array<string, mixed> $budgetTarget
|
||||
* @return array<string, int|float|string>
|
||||
*/
|
||||
public static function evaluateFamilyBudgets(array $familyBudgets, array $durationsByFile): array
|
||||
public static function evaluateBudgetTarget(array $budgetTarget, float $measuredSeconds): array
|
||||
{
|
||||
if (! isset($budgetTarget['targetType'], $budgetTarget['targetId'])) {
|
||||
throw new InvalidArgumentException('Budget targets must define targetType and targetId.');
|
||||
}
|
||||
|
||||
$evaluation = self::fromArray($budgetTarget)->evaluate($measuredSeconds);
|
||||
|
||||
return array_merge([
|
||||
'budgetId' => (string) ($budgetTarget['budgetId'] ?? sprintf('%s-%s', $budgetTarget['targetType'], $budgetTarget['targetId'])),
|
||||
'targetType' => (string) $budgetTarget['targetType'],
|
||||
'targetId' => (string) $budgetTarget['targetId'],
|
||||
], $evaluation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $budgetTargets
|
||||
* @param array<string, float> $classificationTotals
|
||||
* @param array<string, float> $familyTotals
|
||||
* @return list<array<string, int|float|string>>
|
||||
*/
|
||||
public static function evaluateBudgetTargets(
|
||||
array $budgetTargets,
|
||||
float $laneSeconds,
|
||||
array $classificationTotals,
|
||||
array $familyTotals,
|
||||
): array {
|
||||
$evaluations = [];
|
||||
|
||||
foreach ($familyBudgets as $familyBudget) {
|
||||
$matchedSelectors = [];
|
||||
$measuredSeconds = 0.0;
|
||||
$selectorType = (string) ($familyBudget['selectorType'] ?? 'path');
|
||||
$selectors = array_values(array_filter(
|
||||
$familyBudget['selectors'] ?? [],
|
||||
static fn (mixed $selector): bool => is_string($selector) && $selector !== '',
|
||||
));
|
||||
foreach ($budgetTargets as $budgetTarget) {
|
||||
$targetType = (string) ($budgetTarget['targetType'] ?? '');
|
||||
$targetId = (string) ($budgetTarget['targetId'] ?? '');
|
||||
|
||||
foreach ($durationsByFile as $filePath => $duration) {
|
||||
foreach ($selectors as $selector) {
|
||||
$matches = match ($selectorType) {
|
||||
'file' => $filePath === $selector,
|
||||
default => str_starts_with($filePath, rtrim($selector, '/')),
|
||||
};
|
||||
|
||||
if (! $matches) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$matchedSelectors[] = $selector;
|
||||
$measuredSeconds += (float) $duration;
|
||||
|
||||
break;
|
||||
}
|
||||
if ($targetType === '' || $targetId === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$budget = self::fromArray([
|
||||
'thresholdSeconds' => (int) $familyBudget['thresholdSeconds'],
|
||||
'baselineSource' => (string) $familyBudget['baselineSource'],
|
||||
'enforcement' => (string) $familyBudget['enforcement'],
|
||||
'lifecycleState' => (string) $familyBudget['lifecycleState'],
|
||||
]);
|
||||
$measuredSeconds = match ($targetType) {
|
||||
'lane' => $laneSeconds,
|
||||
'classification' => (float) ($classificationTotals[$targetId] ?? 0.0),
|
||||
'family' => (float) ($familyTotals[$targetId] ?? 0.0),
|
||||
default => 0.0,
|
||||
};
|
||||
|
||||
$evaluations[] = array_merge([
|
||||
'familyId' => (string) $familyBudget['familyId'],
|
||||
], $budget->evaluate($measuredSeconds), [
|
||||
'matchedSelectors' => array_values(array_unique($matchedSelectors)),
|
||||
]);
|
||||
$evaluations[] = self::evaluateBudgetTarget($budgetTarget, $measuredSeconds);
|
||||
}
|
||||
|
||||
return $evaluations;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,7 @@ public static function artifactPaths(string $laneId, ?string $artifactDirectory
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{slowestEntries: list<array{subject: string, durationSeconds: float, laneId: string}>, durationsByFile: array<string, float>}
|
||||
* @return array{slowestEntries: list<array<string, mixed>>, durationsByFile: array<string, float>}
|
||||
*/
|
||||
public static function parseJUnit(string $filePath, string $laneId): array
|
||||
{
|
||||
@ -54,19 +54,22 @@ public static function parseJUnit(string $filePath, string $laneId): array
|
||||
foreach ($xml->xpath('//testcase') ?: [] as $testcase) {
|
||||
$rawSubject = trim((string) ($testcase['file'] ?? ''));
|
||||
$subject = $rawSubject !== '' ? $rawSubject : trim((string) ($testcase['name'] ?? 'unknown-testcase'));
|
||||
$duration = (float) ($testcase['time'] ?? 0.0);
|
||||
$duration = round((float) ($testcase['time'] ?? 0.0), 6);
|
||||
$normalizedFile = explode('::', $subject)[0];
|
||||
|
||||
$slowestEntries[] = [
|
||||
'label' => $subject,
|
||||
'subject' => $subject,
|
||||
'durationSeconds' => round($duration, 6),
|
||||
'filePath' => $normalizedFile,
|
||||
'durationSeconds' => $duration,
|
||||
'wallClockSeconds' => $duration,
|
||||
'laneId' => $laneId,
|
||||
];
|
||||
|
||||
$durationsByFile[$normalizedFile] = round(($durationsByFile[$normalizedFile] ?? 0.0) + $duration, 6);
|
||||
}
|
||||
|
||||
usort($slowestEntries, static fn (array $left, array $right): int => $right['durationSeconds'] <=> $left['durationSeconds']);
|
||||
usort($slowestEntries, static fn (array $left, array $right): int => $right['wallClockSeconds'] <=> $left['wallClockSeconds']);
|
||||
|
||||
return [
|
||||
'slowestEntries' => array_slice($slowestEntries, 0, 10),
|
||||
@ -75,7 +78,7 @@ public static function parseJUnit(string $filePath, string $laneId): array
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array{subject: string, durationSeconds: float, laneId: string}> $slowestEntries
|
||||
* @param list<array<string, mixed>> $slowestEntries
|
||||
* @param array<string, float> $durationsByFile
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
@ -88,8 +91,8 @@ public static function buildReport(
|
||||
?string $comparisonProfile = null,
|
||||
): array {
|
||||
$lane = TestLaneManifest::lane($laneId);
|
||||
$budget = TestLaneBudget::fromArray($lane['budget']);
|
||||
$budgetEvaluation = $budget->evaluate($wallClockSeconds);
|
||||
$laneBudget = TestLaneBudget::fromArray($lane['budget']);
|
||||
$laneBudgetEvaluation = $laneBudget->evaluate($wallClockSeconds);
|
||||
$artifactPaths = self::artifactPaths($laneId, $artifactDirectory);
|
||||
$artifacts = [];
|
||||
|
||||
@ -113,22 +116,55 @@ public static function buildReport(
|
||||
];
|
||||
}
|
||||
|
||||
$attribution = self::buildAttribution($durationsByFile);
|
||||
$budgetEvaluations = TestLaneBudget::evaluateBudgetTargets(
|
||||
self::relevantBudgetTargets(
|
||||
$laneId,
|
||||
$attribution['classificationTotals'],
|
||||
$attribution['familyTotals'],
|
||||
),
|
||||
$wallClockSeconds,
|
||||
$attribution['classificationTotals'],
|
||||
$attribution['familyTotals'],
|
||||
);
|
||||
|
||||
$enrichedSlowestEntries = array_values(array_map(
|
||||
static function (array $entry) use ($attribution): array {
|
||||
$filePath = (string) ($entry['filePath'] ?? '');
|
||||
$familyId = $attribution['fileToFamily'][$filePath] ?? null;
|
||||
$classificationId = $attribution['fileToClassification'][$filePath] ?? null;
|
||||
|
||||
return array_filter(array_merge($entry, [
|
||||
'familyId' => $familyId,
|
||||
'classificationId' => $classificationId,
|
||||
]), static fn (mixed $value): bool => $value !== null);
|
||||
},
|
||||
$slowestEntries,
|
||||
));
|
||||
|
||||
$report = [
|
||||
'laneId' => $laneId,
|
||||
'artifactDirectory' => trim($artifactDirectory ?? TestLaneManifest::artifactDirectory(), '/'),
|
||||
'finishedAt' => gmdate('c'),
|
||||
'wallClockSeconds' => round($wallClockSeconds, 6),
|
||||
'budgetThresholdSeconds' => $budget->thresholdSeconds,
|
||||
'budgetBaselineSource' => $budget->baselineSource,
|
||||
'budgetEnforcement' => $budget->enforcement,
|
||||
'budgetLifecycleState' => $budget->lifecycleState,
|
||||
'budgetStatus' => $budgetEvaluation['budgetStatus'],
|
||||
'slowestEntries' => array_values($slowestEntries),
|
||||
'familyBudgetEvaluations' => TestLaneBudget::evaluateFamilyBudgets(TestLaneManifest::familyBudgets(), $durationsByFile),
|
||||
'budgetThresholdSeconds' => $laneBudget->thresholdSeconds,
|
||||
'budgetBaselineSource' => $laneBudget->baselineSource,
|
||||
'budgetEnforcement' => $laneBudget->enforcement,
|
||||
'budgetLifecycleState' => $laneBudget->lifecycleState,
|
||||
'budgetStatus' => $laneBudgetEvaluation['budgetStatus'],
|
||||
'slowestEntries' => $enrichedSlowestEntries,
|
||||
'classificationAttribution' => $attribution['classificationAttribution'],
|
||||
'familyAttribution' => $attribution['familyAttribution'],
|
||||
'budgetEvaluations' => $budgetEvaluations,
|
||||
'familyBudgetEvaluations' => array_values(array_filter(
|
||||
$budgetEvaluations,
|
||||
static fn (array $evaluation): bool => ($evaluation['targetType'] ?? null) === 'family',
|
||||
)),
|
||||
'artifacts' => $artifacts,
|
||||
];
|
||||
|
||||
if ($budget->baselineDeltaTargetPercent !== null) {
|
||||
$report['baselineDeltaTargetPercent'] = $budget->baselineDeltaTargetPercent;
|
||||
if ($laneBudget->baselineDeltaTargetPercent !== null) {
|
||||
$report['baselineDeltaTargetPercent'] = $laneBudget->baselineDeltaTargetPercent;
|
||||
}
|
||||
|
||||
$comparison = self::buildSharedFixtureSlimmingComparison($laneId, $wallClockSeconds, $comparisonProfile);
|
||||
@ -163,12 +199,16 @@ public static function writeArtifacts(
|
||||
TestLaneManifest::absolutePath($artifactPaths['budget']),
|
||||
json_encode([
|
||||
'laneId' => $report['laneId'],
|
||||
'artifactDirectory' => $report['artifactDirectory'],
|
||||
'wallClockSeconds' => $report['wallClockSeconds'],
|
||||
'budgetThresholdSeconds' => $report['budgetThresholdSeconds'],
|
||||
'budgetBaselineSource' => $report['budgetBaselineSource'],
|
||||
'budgetEnforcement' => $report['budgetEnforcement'],
|
||||
'budgetLifecycleState' => $report['budgetLifecycleState'],
|
||||
'budgetStatus' => $report['budgetStatus'],
|
||||
'classificationAttribution' => $report['classificationAttribution'],
|
||||
'familyAttribution' => $report['familyAttribution'],
|
||||
'budgetEvaluations' => $report['budgetEvaluations'],
|
||||
'familyBudgetEvaluations' => $report['familyBudgetEvaluations'],
|
||||
'sharedFixtureSlimmingComparison' => $report['sharedFixtureSlimmingComparison'] ?? null,
|
||||
], JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR),
|
||||
@ -194,8 +234,7 @@ public static function finalizeLane(
|
||||
float $wallClockSeconds,
|
||||
string $capturedOutput = '',
|
||||
?string $comparisonProfile = null,
|
||||
): array
|
||||
{
|
||||
): array {
|
||||
$artifactPaths = self::artifactPaths($laneId);
|
||||
$parsed = self::parseJUnit(TestLaneManifest::absolutePath($artifactPaths['junit']), $laneId);
|
||||
$report = self::buildReport(
|
||||
@ -240,12 +279,128 @@ private static function buildSummaryMarkdown(array $report): string
|
||||
$lines[] = '## Slowest entries';
|
||||
|
||||
foreach ($report['slowestEntries'] as $entry) {
|
||||
$lines[] = sprintf('- %s (%.2fs)', $entry['subject'], (float) $entry['durationSeconds']);
|
||||
$label = (string) ($entry['label'] ?? $entry['subject'] ?? 'unknown');
|
||||
$lines[] = sprintf('- %s (%.2fs)', $label, (float) ($entry['wallClockSeconds'] ?? $entry['durationSeconds'] ?? 0.0));
|
||||
}
|
||||
|
||||
if (($report['classificationAttribution'] ?? []) !== []) {
|
||||
$lines[] = '';
|
||||
$lines[] = '## Classification attribution';
|
||||
|
||||
foreach ($report['classificationAttribution'] as $entry) {
|
||||
$lines[] = sprintf('- %s (%.2fs)', $entry['classificationId'], (float) $entry['totalWallClockSeconds']);
|
||||
}
|
||||
}
|
||||
|
||||
if (($report['familyAttribution'] ?? []) !== []) {
|
||||
$lines[] = '';
|
||||
$lines[] = '## Family attribution';
|
||||
|
||||
foreach ($report['familyAttribution'] as $entry) {
|
||||
$lines[] = sprintf('- %s [%s] (%.2fs)', $entry['familyId'], $entry['classificationId'], (float) $entry['totalWallClockSeconds']);
|
||||
}
|
||||
}
|
||||
|
||||
return implode(PHP_EOL, $lines).PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function buildAttribution(array $durationsByFile): array
|
||||
{
|
||||
$classificationTotals = [];
|
||||
$familyTotals = [];
|
||||
$classificationHotspots = [];
|
||||
$familyHotspots = [];
|
||||
$fileToClassification = [];
|
||||
$fileToFamily = [];
|
||||
|
||||
foreach ($durationsByFile as $filePath => $duration) {
|
||||
$family = TestLaneManifest::familyForFile($filePath);
|
||||
$mixedResolution = TestLaneManifest::mixedFileResolution($filePath);
|
||||
$classificationId = $mixedResolution['primaryClassificationId'] ?? ($family['classificationId'] ?? null);
|
||||
|
||||
if (! is_string($classificationId) || $classificationId === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$classificationTotals[$classificationId] = round(($classificationTotals[$classificationId] ?? 0.0) + $duration, 6);
|
||||
$classificationHotspots[$classificationId][] = $filePath;
|
||||
$fileToClassification[$filePath] = $classificationId;
|
||||
|
||||
if (is_array($family)) {
|
||||
$familyId = (string) $family['familyId'];
|
||||
$familyTotals[$familyId] = round(($familyTotals[$familyId] ?? 0.0) + $duration, 6);
|
||||
$familyHotspots[$familyId]['classificationId'] = $family['classificationId'];
|
||||
$familyHotspots[$familyId]['hotspotFiles'][] = $filePath;
|
||||
$fileToFamily[$filePath] = $familyId;
|
||||
}
|
||||
}
|
||||
|
||||
$classificationAttribution = array_values(array_map(
|
||||
static fn (string $classificationId, float $total): array => [
|
||||
'classificationId' => $classificationId,
|
||||
'totalWallClockSeconds' => $total,
|
||||
'hotspotFiles' => array_values(array_unique($classificationHotspots[$classificationId] ?? [])),
|
||||
],
|
||||
array_keys($classificationTotals),
|
||||
$classificationTotals,
|
||||
));
|
||||
|
||||
usort($classificationAttribution, static fn (array $left, array $right): int => $right['totalWallClockSeconds'] <=> $left['totalWallClockSeconds']);
|
||||
|
||||
$familyAttribution = array_values(array_map(
|
||||
static function (string $familyId, float $total) use ($familyHotspots): array {
|
||||
return [
|
||||
'familyId' => $familyId,
|
||||
'classificationId' => $familyHotspots[$familyId]['classificationId'],
|
||||
'totalWallClockSeconds' => $total,
|
||||
'hotspotFiles' => array_values(array_unique($familyHotspots[$familyId]['hotspotFiles'] ?? [])),
|
||||
];
|
||||
},
|
||||
array_keys($familyTotals),
|
||||
$familyTotals,
|
||||
));
|
||||
|
||||
usort($familyAttribution, static fn (array $left, array $right): int => $right['totalWallClockSeconds'] <=> $left['totalWallClockSeconds']);
|
||||
|
||||
return [
|
||||
'classificationTotals' => $classificationTotals,
|
||||
'familyTotals' => $familyTotals,
|
||||
'classificationAttribution' => $classificationAttribution,
|
||||
'familyAttribution' => $familyAttribution,
|
||||
'fileToClassification' => $fileToClassification,
|
||||
'fileToFamily' => $fileToFamily,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, float> $classificationTotals
|
||||
* @param array<string, float> $familyTotals
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
private static function relevantBudgetTargets(
|
||||
string $laneId,
|
||||
array $classificationTotals,
|
||||
array $familyTotals,
|
||||
): array {
|
||||
return array_values(array_filter(
|
||||
TestLaneManifest::budgetTargets(),
|
||||
static function (array $budgetTarget) use ($laneId, $classificationTotals, $familyTotals): bool {
|
||||
$targetType = (string) ($budgetTarget['targetType'] ?? '');
|
||||
$targetId = (string) ($budgetTarget['targetId'] ?? '');
|
||||
|
||||
return match ($targetType) {
|
||||
'lane' => $targetId === $laneId,
|
||||
'classification' => array_key_exists($targetId, $classificationTotals),
|
||||
'family' => array_key_exists($targetId, $familyTotals),
|
||||
default => false,
|
||||
};
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, float|int|string>|null
|
||||
*/
|
||||
|
||||
@ -25,25 +25,33 @@
|
||||
->and($hardFailBudget->evaluate(31.2)['budgetStatus'])->toBe('over-budget');
|
||||
});
|
||||
|
||||
it('evaluates initial family thresholds from matched file durations', function (): void {
|
||||
$evaluations = TestLaneBudget::evaluateFamilyBudgets(
|
||||
TestLaneManifest::familyBudgets(),
|
||||
it('evaluates family targets through the generic budget target path', function (): void {
|
||||
$familyTargets = array_values(array_filter(
|
||||
TestLaneManifest::budgetTargets(),
|
||||
static fn (array $target): bool => $target['targetType'] === 'family',
|
||||
));
|
||||
|
||||
$evaluations = TestLaneBudget::evaluateBudgetTargets(
|
||||
$familyTargets,
|
||||
0.0,
|
||||
[],
|
||||
[
|
||||
'tests/Feature/OpsUx/OperateHubShellTest.php' => 18.4,
|
||||
'tests/Feature/Guards/ActionSurfaceContractTest.php' => 7.8,
|
||||
'tests/Browser/Spec198MonitoringPageStateSmokeTest.php' => 14.2,
|
||||
'ops-ux-governance' => 18.4,
|
||||
'action-surface-contract' => 7.8,
|
||||
'browser-smoke' => 14.2,
|
||||
],
|
||||
);
|
||||
|
||||
expect($evaluations)->not->toBeEmpty()
|
||||
->and($evaluations[0])->toHaveKeys([
|
||||
'familyId',
|
||||
'budgetId',
|
||||
'targetType',
|
||||
'targetId',
|
||||
'thresholdSeconds',
|
||||
'baselineSource',
|
||||
'enforcement',
|
||||
'lifecycleState',
|
||||
'measuredSeconds',
|
||||
'budgetStatus',
|
||||
'matchedSelectors',
|
||||
]);
|
||||
});
|
||||
@ -3,7 +3,7 @@ # Product Roadmap
|
||||
> Strategic thematic blocks and release trajectory.
|
||||
> This is the "big picture" — not individual specs.
|
||||
|
||||
**Last updated**: 2026-04-12
|
||||
**Last updated**: 2026-04-17
|
||||
|
||||
---
|
||||
|
||||
@ -76,6 +76,15 @@ ### R2 Completion — Evidence & Exception Workflows
|
||||
- Formal "evidence pack" entity → **Not yet specced**
|
||||
- Workspace-level PII override for review packs → deferred from 109
|
||||
|
||||
### Findings Workflow v2 / Execution Layer
|
||||
Turn findings from a reviewable register into an accountable operating flow with clear ownership, personal queues, intake, hygiene, and minimal escalation.
|
||||
**Scope direction**:
|
||||
- Clarify owner versus assignee semantics and accountability language first
|
||||
- Add operator work surfaces such as "Assigned to me" and an unassigned intake queue with basic claim flow
|
||||
- Harden assignment hygiene, stale-work detection, and resolved-versus-verified outcome semantics
|
||||
- Reuse the existing alerting foundation for assignment, reopen, due-soon, and overdue notification flows
|
||||
- Keep comments, external ticket handoff, and cross-tenant workboards as later slices instead of forcing them into the first workflow iteration
|
||||
|
||||
### Policy Lifecycle / Ghost Policies
|
||||
Soft delete detection, automatic restore, "Deleted" badge, restore from backup.
|
||||
Draft exists (Spec 900). Needs spec refresh and prioritization.
|
||||
@ -101,6 +110,7 @@ ### MSP Portfolio & Operations (Multi-Tenant)
|
||||
Multi-tenant health dashboard, SLA/compliance reports (PDF), cross-tenant troubleshooting center.
|
||||
**Source**: 0800-future-features brainstorming, identified as highest priority pillar.
|
||||
**Prerequisites**: Decision-Based Operating Foundations, Cross-tenant compare (Spec 043 — draft only).
|
||||
**Later expansion**: portfolio operations should eventually include a cross-tenant findings workboard once tenant-level inbox and intake flows are stable.
|
||||
|
||||
### Human-in-the-Loop Autonomous Governance (Decision-Based Operating)
|
||||
Continuous detection, triage, decision drafting, approval-driven execution, and closed-loop evidence for governance actions across the workspace portfolio.
|
||||
@ -118,6 +128,10 @@ ### Standardization & Policy Quality ("Intune Linting")
|
||||
Policy linter (naming, scope tag requirements, no All-Users on high-risk), company standards as templates, policy hygiene (duplicate finder, unassigned, orphaned, stale).
|
||||
**Source**: 0800-future-features brainstorming.
|
||||
|
||||
### PSA / Ticketing Handoff
|
||||
Outbound handoff from findings into external service-desk or PSA systems with visible `ticket_ref` linkage and auditable "ticket created/linked" events.
|
||||
**Scope direction**: start with one-way handoff and internal visibility, not full bidirectional sync or full ITSM modeling.
|
||||
|
||||
### Compliance Readiness & Executive Review Packs
|
||||
On-demand review packs that combine governance findings, accepted risks, evidence, baseline/drift posture, and key security signals into one coherent deliverable. BSI-/NIS2-/CIS-oriented readiness views (without certification claims). Executive / CISO / customer-facing report surfaces alongside operator-facing detail views. Exportable auditor-ready and management-ready outputs.
|
||||
**Goal**: Make TenantPilot sellable as an MSP-facing governance and review platform for German midmarket and compliance-oriented customers who want structured tenant reviews and management-ready outputs on demand.
|
||||
|
||||
@ -5,7 +5,7 @@ # Spec Candidates
|
||||
>
|
||||
> **Flow**: Inbox → Qualified → Planned → Spec created → moved to `Promoted to Spec`
|
||||
|
||||
**Last reviewed**: 2026-04-12 (added decision-based operating foundations and autonomous governance track)
|
||||
**Last reviewed**: 2026-04-17 (added findings execution layer cluster)
|
||||
|
||||
---
|
||||
|
||||
@ -579,6 +579,113 @@ ### Exception / Risk-Acceptance Workflow for Findings
|
||||
- **Dependencies**: Findings workflow (Spec 111) complete, audit log foundation (Spec 134)
|
||||
- **Priority**: high
|
||||
|
||||
> Findings execution layer cluster: complementary to the existing risk-acceptance candidate. Keep these split so prioritization can pull workflow semantics, operator work surfaces, alerts, external handoff, and later portfolio operating slices independently instead of collapsing them into one oversized "Findings v2" spec.
|
||||
|
||||
### Finding Ownership Semantics Clarification
|
||||
- **Type**: domain semantics / workflow hardening
|
||||
- **Source**: findings execution layer candidate pack 2026-04-17; current Finding owner/assignee overlap analysis
|
||||
- **Problem**: Finding already models `owner` and `assignee`, but the semantic split is not crisp enough to support inboxes, escalation, stale-work detection, or consistent audit language. Accountability and active execution responsibility can currently blur together in UI copy, policies, and workflow rules.
|
||||
- **Why it matters**: Without a shared contract, every downstream workflow slice will invent its own meaning for owner versus assignee. That produces ambiguous queues, brittle escalation rules, and inconsistent governance language.
|
||||
- **Proposed direction**: Define canonical semantics for accountability owner versus active assignee; align labels, policies, audit/event vocabulary, and permission expectations around that split; encode expected lifecycle states for unassigned, assigned, reassigned, and orphaned work without introducing team-queue abstractions yet.
|
||||
- **Explicit non-goals**: Team-/queue-based ownership, ticketing, comments, and notification delivery.
|
||||
- **Dependencies**: Existing Findings model and workflow state machine, Findings UI surfaces, audit vocabulary.
|
||||
- **Roadmap fit**: Findings Workflow v2 hardening lane.
|
||||
- **Strategic sequencing**: First. The rest of the findings execution layer consumes this decision.
|
||||
- **Priority**: high
|
||||
|
||||
### Findings Operator Inbox v1
|
||||
- **Type**: operator surface / workflow execution
|
||||
- **Source**: findings execution layer candidate pack 2026-04-17; gap between assignment auditability and day-to-day operator flow
|
||||
- **Problem**: Findings can be assigned, but TenantPilot lacks a personal work surface that turns assignment into a real operator queue. Assigned work is still discovered indirectly through broader tenant or findings lists.
|
||||
- **Why it matters**: Without a dedicated personal queue, assignment remains metadata instead of operational flow. A "My Work" surface is the simplest bridge from governance data to daily execution.
|
||||
- **Proposed direction**: Add a workspace-level or otherwise permission-safe "My Findings / My Work" surface for the current user; emphasize open, due, overdue, high-severity, and reopened findings; provide fast drilldown into the finding record; add a small "assigned to me" dashboard signal; reuse existing RBAC and finding visibility rules instead of inventing a second permission system.
|
||||
- **Explicit non-goals**: Team queues, complex routing rules, external ticketing, and multi-step approval chains.
|
||||
- **Dependencies**: Ownership semantics, `assignee_user_id`, `due_at`, finding status logic, RBAC on finding read/open.
|
||||
- **Roadmap fit**: Findings Workflow v2; feeds later governance inbox work.
|
||||
- **Strategic sequencing**: Second, ideally immediately after ownership semantics.
|
||||
- **Priority**: high
|
||||
|
||||
### Findings Intake & Team Queue v1
|
||||
- **Type**: workflow execution / team operations
|
||||
- **Source**: findings execution layer candidate pack 2026-04-17; missing intake surface before personal assignment
|
||||
- **Problem**: A personal inbox does not solve how new or unassigned findings enter the workflow. Operators need an intake surface before work is personally assigned.
|
||||
- **Why it matters**: Without intake, backlog triage stays hidden in general-purpose lists and unassigned work becomes easy to ignore or duplicate.
|
||||
- **Proposed direction**: Introduce unassigned and needs-triage views, an optional claim action, and basic shared-worklist conventions; use filters or tabs that clearly separate intake from active execution; make the difference between unowned backlog and personally assigned work explicit.
|
||||
- **Explicit non-goals**: Full team model, capacity planning, auto-routing, and load-balancing logic.
|
||||
- **Dependencies**: Ownership semantics, findings filters/tabs, open-status definitions.
|
||||
- **Roadmap fit**: Findings Workflow v2; prerequisite for a broader team operating model.
|
||||
- **Strategic sequencing**: Third, after personal inbox foundations exist.
|
||||
- **Priority**: high
|
||||
|
||||
### Findings Notifications & Escalation v1
|
||||
- **Type**: alerts / workflow execution
|
||||
- **Source**: findings execution layer candidate pack 2026-04-17; gap between assignment metadata and actionable control loop
|
||||
- **Problem**: Assignment, reopen, due, and overdue states currently risk becoming silent metadata unless operators keep polling findings views.
|
||||
- **Why it matters**: Due dates without reminders or escalation are visibility, not control. Existing alert foundations only create operator value if findings workflow emits actionable events.
|
||||
- **Proposed direction**: Add notifications for assignment, system-driven reopen, due-soon, and overdue states; introduce minimal escalation to owner or a defined role; explicitly consume the existing alert and notification infrastructure rather than building a findings-specific delivery system.
|
||||
- **Explicit non-goals**: Multi-stage escalation chains, a large notification-preference center, and bidirectional ticket synchronization.
|
||||
- **Dependencies**: Ownership semantics, operator inbox/intake surfaces, due/SLA logic, alert plumbing.
|
||||
- **Roadmap fit**: Findings workflow hardening on top of the existing alerting foundation.
|
||||
- **Strategic sequencing**: After inbox and intake exist so notifications land on meaningful destinations.
|
||||
- **Priority**: high
|
||||
|
||||
### Assignment Hygiene & Stale Work Detection
|
||||
- **Type**: workflow hardening / operations hygiene
|
||||
- **Source**: findings execution layer candidate pack 2026-04-17; assignment lifecycle hygiene gap analysis
|
||||
- **Problem**: Assignments can silently rot when memberships change, assignees lose access, or findings remain stuck in `in_progress` indefinitely.
|
||||
- **Why it matters**: Stale and orphaned assignments erode trust in queues and create hidden backlog. Hygiene reporting is a prerequisite for later auto-reassign logic.
|
||||
- **Proposed direction**: Detect orphaned assignments, invalid or inactive assignees, and stale `in_progress` work; provide an admin/operator hygiene report; define what counts as stale versus active; stop short of automatic redistribution in v1.
|
||||
- **Explicit non-goals**: Full reassignment workflows, automatic load distribution, and absence management.
|
||||
- **Dependencies**: Tenant membership / RBAC model, scheduler or job layer, ownership semantics, open status logic.
|
||||
- **Roadmap fit**: Findings Workflow v2 hardening.
|
||||
- **Strategic sequencing**: Shortly after ownership semantics, ideally alongside or immediately after notifications.
|
||||
- **Priority**: high
|
||||
|
||||
### Finding Outcome Taxonomy & Verification Semantics
|
||||
- **Type**: workflow semantics / reporting hardening
|
||||
- **Source**: findings execution layer candidate pack 2026-04-17; status/outcome reporting gap analysis
|
||||
- **Problem**: Resolve and close reasoning is too free-form, and the product does not cleanly separate operator-resolved from system-verified or confirmed-cleared states.
|
||||
- **Why it matters**: Reporting, audit, reopening logic, and governance review packs need structured outcomes rather than ad hoc prose. Otherwise outcome meaning drifts between operators and surfaces.
|
||||
- **Proposed direction**: Define structured reason codes for resolve, close, and reopen transitions; distinguish resolved, verified or confirmed cleared, closed, false positive, duplicate, and no-longer-applicable semantics; make reporting and filter UI consume the taxonomy instead of relying on free text.
|
||||
- **Explicit non-goals**: Comments, full narrative case notes, and complex multi-reason models.
|
||||
- **Dependencies**: Finding status transitions, audit payloads, reporting and filter UI.
|
||||
- **Roadmap fit**: Findings Workflow v2 hardening and downstream review/reporting quality.
|
||||
- **Strategic sequencing**: After the first operator work surfaces unless reporting pressure pulls it earlier.
|
||||
- **Priority**: high
|
||||
|
||||
### Finding Comments & Decision Log v1
|
||||
- **Type**: collaboration / audit depth
|
||||
- **Source**: findings execution layer candidate pack 2026-04-17; operator handoff and context gap analysis
|
||||
- **Problem**: Audit can show that a transition happened, but not the day-to-day operator reasoning or handover context behind triage, resolve, close, or risk-accept decisions.
|
||||
- **Why it matters**: Human workflow needs concise contextual notes that do not belong in status fields or reason-code taxonomies. Without them, operator handover quality stays low.
|
||||
- **Proposed direction**: Add comments and lightweight decision-log entries on findings; surface them in a timeline alongside immutable audit events; use them to support triage, handover, and rationale capture without turning findings into a chat product.
|
||||
- **Explicit non-goals**: `@mentions`, attachments, chat, and realtime collaboration.
|
||||
- **Dependencies**: Finding detail surface, audit/timeline rendering, RBAC.
|
||||
- **Roadmap fit**: Spec-candidate only for now; not required as a standalone roadmap theme.
|
||||
- **Priority**: medium
|
||||
|
||||
### Findings External Ticket Handoff v1
|
||||
- **Type**: external integration / execution handoff
|
||||
- **Source**: findings execution layer candidate pack 2026-04-17; MSP/enterprise workflow alignment gap
|
||||
- **Problem**: Enterprise and MSP operators often need to hand findings into external service-desk or PSA workflows, but the current findings model has no first-class outbound ticket link or handoff state.
|
||||
- **Why it matters**: Outbound handoff is a sellable bridge between TenantPilot governance and existing customer operating models. Without it, findings remain operationally isolated from the systems where remediation work actually gets tracked.
|
||||
- **Proposed direction**: Add an external ticket reference and link on findings, support simple outbound handoff or "ticket created/linked" flows, and audit those transitions explicitly; make internal versus external execution state visible without promising full synchronization.
|
||||
- **Explicit non-goals**: Bidirectional sync, OAuth-native integrations, and full ITSM domain modeling.
|
||||
- **Dependencies**: Findings UI, workspace settings or handoff target configuration, audit events, stable ownership semantics.
|
||||
- **Roadmap fit**: Future PSA/ticketing lane.
|
||||
- **Priority**: medium
|
||||
|
||||
### Cross-Tenant Findings Workboard v1
|
||||
- **Type**: MSP / portfolio operations
|
||||
- **Source**: findings execution layer candidate pack 2026-04-17; portfolio-scale findings operations gap
|
||||
- **Problem**: Once operators manage many tenants, tenant-local inboxes and queues stop scaling. There is no cross-tenant work surface for open findings across a workspace or portfolio.
|
||||
- **Why it matters**: MSP portfolio work requires cross-tenant prioritization by severity, due date, assignee, and tenant. This is the operational complement to a portfolio dashboard.
|
||||
- **Proposed direction**: Add a cross-tenant findings workboard or queue for open findings with filters for severity, due date, assignee, tenant, and status; preserve tenant drilldown and RBAC boundaries; position it as the portfolio-operating surface next to the dashboard, not a replacement for per-tenant detail.
|
||||
- **Explicit non-goals**: Rollout orchestration, full portfolio remediation planning, and team capacity views.
|
||||
- **Dependencies**: Operator inbox, intake queue, notifications/escalation, workspace-level finding visibility, cross-tenant RBAC semantics.
|
||||
- **Roadmap fit**: MSP portfolio and operations.
|
||||
- **Priority**: medium-low
|
||||
|
||||
### Compliance Control Catalog & Interpretation Foundation
|
||||
- **Type**: foundation
|
||||
- **Source**: roadmap/principles alignment 2026-04-10, compliance modeling discussion, future framework-oriented readiness planning
|
||||
|
||||
@ -7,6 +7,28 @@ ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
APP_DIR="${ROOT_DIR}/apps/platform"
|
||||
LANE="${1:-fast-feedback}"
|
||||
|
||||
case "${LANE}" in
|
||||
fast-feedback|fast|default)
|
||||
COMPOSER_SCRIPT="test:report"
|
||||
;;
|
||||
confidence)
|
||||
COMPOSER_SCRIPT="test:report:confidence"
|
||||
;;
|
||||
browser)
|
||||
COMPOSER_SCRIPT="test:report:browser"
|
||||
;;
|
||||
heavy-governance|heavy)
|
||||
COMPOSER_SCRIPT="test:report:heavy"
|
||||
;;
|
||||
profiling|profile)
|
||||
COMPOSER_SCRIPT="test:report:profile"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown test lane: ${LANE}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
cd "${APP_DIR}"
|
||||
|
||||
exec ./vendor/bin/sail php -r 'require "vendor/autoload.php"; exit(\Tests\Support\TestLaneManifest::renderLatestReport((string) ($argv[1] ?? ""), (string) ($argv[2] ?? "shared-test-fixture-slimming")));' "${LANE}" "shared-test-fixture-slimming"
|
||||
exec ./vendor/bin/sail composer run --timeout=0 "${COMPOSER_SCRIPT}"
|
||||
@ -0,0 +1,36 @@
|
||||
# Specification Quality Checklist: Filament/Livewire Heavy Suite Segmentation
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-04-16
|
||||
**Feature**: [spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Validation completed in one pass on 2026-04-16.
|
||||
- Filament and Livewire are used as scope-defining names for the affected test families, not as implementation prescriptions.
|
||||
- The specification remains bounded to repository test-governance behavior and leaves CI wiring to the follow-up planning sequence.
|
||||
@ -0,0 +1,666 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: Heavy Suite Segmentation Logical Contract
|
||||
version: 1.0.0
|
||||
summary: Logical contract for classifying heavy UI test families, validating lane placement, and reading heavy-attribution reports.
|
||||
description: |
|
||||
This is a logical contract for repository tooling, tests, and planning artifacts.
|
||||
It does not imply a new runtime HTTP service. It documents the expected
|
||||
semantics of heavy-family classification, lane-placement validation, and
|
||||
heavy-lane report attribution so the existing lane-governance seams remain
|
||||
consistent as Spec 208 is implemented.
|
||||
x-logical-contract: true
|
||||
servers:
|
||||
- url: https://tenantatlas.local/logical
|
||||
paths:
|
||||
/heavy-test-classifications:
|
||||
get:
|
||||
summary: Read the checked-in heavy classification catalog.
|
||||
operationId: listHeavyTestClassifications
|
||||
responses:
|
||||
'200':
|
||||
description: Current heavy classification catalog.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- classifications
|
||||
properties:
|
||||
classifications:
|
||||
type: array
|
||||
minItems: 5
|
||||
items:
|
||||
$ref: '#/components/schemas/HeavyTestClassification'
|
||||
/heavy-test-placement-rules:
|
||||
get:
|
||||
summary: Read the checked-in lane placement rules.
|
||||
operationId: listHeavyTestPlacementRules
|
||||
responses:
|
||||
'200':
|
||||
description: Current lane placement rules.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- rules
|
||||
properties:
|
||||
rules:
|
||||
type: array
|
||||
minItems: 5
|
||||
items:
|
||||
$ref: '#/components/schemas/LanePlacementRule'
|
||||
/heavy-test-families:
|
||||
get:
|
||||
summary: Read the seeded heavy test family inventory.
|
||||
operationId: listHeavyTestFamilies
|
||||
responses:
|
||||
'200':
|
||||
description: Current heavy family inventory.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- families
|
||||
properties:
|
||||
families:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
$ref: '#/components/schemas/HeavyTestFamily'
|
||||
/heavy-test-families/mixed-file-resolutions:
|
||||
get:
|
||||
summary: Read the checked-in mixed-file resolution inventory.
|
||||
operationId: listMixedFileResolutions
|
||||
responses:
|
||||
'200':
|
||||
description: Current mixed-file resolution records.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- resolutions
|
||||
properties:
|
||||
resolutions:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/MixedFileResolution'
|
||||
/heavy-test-families/placement-validations:
|
||||
post:
|
||||
summary: Validate that a heavy class or family is assigned to a compatible lane.
|
||||
operationId: validateHeavyFamilyPlacement
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LanePlacementValidationRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Placement validation result.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LanePlacementValidationResult'
|
||||
/test-lanes/{laneId}/reports/heavy-attribution/latest:
|
||||
get:
|
||||
summary: Read the latest heavy-attribution report for a lane.
|
||||
operationId: getLatestHeavyAttributionReport
|
||||
parameters:
|
||||
- name: laneId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/components/schemas/LaneId'
|
||||
responses:
|
||||
'200':
|
||||
description: Latest heavy-attribution report for the requested lane.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HeavyAttributionReport'
|
||||
components:
|
||||
schemas:
|
||||
ClassificationId:
|
||||
type: string
|
||||
enum:
|
||||
- ui-light
|
||||
- ui-workflow
|
||||
- surface-guard
|
||||
- discovery-heavy
|
||||
- browser
|
||||
LaneId:
|
||||
type: string
|
||||
enum:
|
||||
- fast-feedback
|
||||
- confidence
|
||||
- heavy-governance
|
||||
- browser
|
||||
- profiling
|
||||
- junit
|
||||
PlacementAllowance:
|
||||
type: string
|
||||
enum:
|
||||
- required
|
||||
- allowed
|
||||
- discouraged
|
||||
- forbidden
|
||||
ValidationStatus:
|
||||
type: string
|
||||
enum:
|
||||
- seeded
|
||||
- reviewed
|
||||
- migrated
|
||||
- guarded
|
||||
BudgetStatus:
|
||||
type: string
|
||||
enum:
|
||||
- within-budget
|
||||
- warning
|
||||
- over-budget
|
||||
BudgetBaselineSource:
|
||||
type: string
|
||||
enum:
|
||||
- measured-current-suite
|
||||
- measured-lane
|
||||
- measured-post-spec-207
|
||||
BudgetEnforcement:
|
||||
type: string
|
||||
enum:
|
||||
- report-only
|
||||
- warn
|
||||
- hard-fail
|
||||
BudgetLifecycleState:
|
||||
type: string
|
||||
enum:
|
||||
- draft
|
||||
- measured
|
||||
- documented
|
||||
- enforced
|
||||
HeavyFamilySelector:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- selectorType
|
||||
- selectorValue
|
||||
- selectorRole
|
||||
- sourceOfTruth
|
||||
properties:
|
||||
selectorType:
|
||||
type: string
|
||||
enum:
|
||||
- suite
|
||||
- path
|
||||
- group
|
||||
- file
|
||||
selectorValue:
|
||||
type: string
|
||||
minLength: 1
|
||||
selectorRole:
|
||||
type: string
|
||||
enum:
|
||||
- include
|
||||
- exclude
|
||||
- inventory-only
|
||||
sourceOfTruth:
|
||||
type: string
|
||||
enum:
|
||||
- manifest
|
||||
- pest-group
|
||||
- guard-test
|
||||
- report-attribution
|
||||
rationale:
|
||||
type: string
|
||||
HeavyTestClassification:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- classificationId
|
||||
- purpose
|
||||
- dominantCostDrivers
|
||||
- defaultLaneId
|
||||
- allowedLaneIds
|
||||
- forbiddenLaneIds
|
||||
- reviewerSignals
|
||||
- escalationTriggers
|
||||
properties:
|
||||
classificationId:
|
||||
$ref: '#/components/schemas/ClassificationId'
|
||||
purpose:
|
||||
type: string
|
||||
dominantCostDrivers:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
defaultLaneId:
|
||||
$ref: '#/components/schemas/LaneId'
|
||||
allowedLaneIds:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
$ref: '#/components/schemas/LaneId'
|
||||
forbiddenLaneIds:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/LaneId'
|
||||
reviewerSignals:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
escalationTriggers:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
classificationId:
|
||||
const: browser
|
||||
then:
|
||||
properties:
|
||||
defaultLaneId:
|
||||
const: browser
|
||||
allowedLaneIds:
|
||||
type: array
|
||||
minItems: 1
|
||||
maxItems: 1
|
||||
items:
|
||||
const: browser
|
||||
- if:
|
||||
properties:
|
||||
classificationId:
|
||||
const: surface-guard
|
||||
then:
|
||||
properties:
|
||||
defaultLaneId:
|
||||
const: heavy-governance
|
||||
forbiddenLaneIds:
|
||||
allOf:
|
||||
- contains:
|
||||
const: fast-feedback
|
||||
- contains:
|
||||
const: confidence
|
||||
- if:
|
||||
properties:
|
||||
classificationId:
|
||||
const: ui-workflow
|
||||
then:
|
||||
properties:
|
||||
defaultLaneId:
|
||||
const: confidence
|
||||
- if:
|
||||
properties:
|
||||
classificationId:
|
||||
const: discovery-heavy
|
||||
then:
|
||||
properties:
|
||||
defaultLaneId:
|
||||
const: heavy-governance
|
||||
forbiddenLaneIds:
|
||||
allOf:
|
||||
- contains:
|
||||
const: fast-feedback
|
||||
- contains:
|
||||
const: confidence
|
||||
HeavyTestFamily:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- familyId
|
||||
- classificationId
|
||||
- purpose
|
||||
- currentLaneId
|
||||
- targetLaneId
|
||||
- selectors
|
||||
- hotspotFiles
|
||||
- costSignals
|
||||
- validationStatus
|
||||
properties:
|
||||
familyId:
|
||||
type: string
|
||||
classificationId:
|
||||
$ref: '#/components/schemas/ClassificationId'
|
||||
purpose:
|
||||
type: string
|
||||
currentLaneId:
|
||||
$ref: '#/components/schemas/LaneId'
|
||||
targetLaneId:
|
||||
$ref: '#/components/schemas/LaneId'
|
||||
selectors:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
$ref: '#/components/schemas/HeavyFamilySelector'
|
||||
hotspotFiles:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
costSignals:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
confidenceRationale:
|
||||
type: string
|
||||
validationStatus:
|
||||
$ref: '#/components/schemas/ValidationStatus'
|
||||
allOf:
|
||||
- if:
|
||||
properties:
|
||||
targetLaneId:
|
||||
const: confidence
|
||||
then:
|
||||
required:
|
||||
- confidenceRationale
|
||||
properties:
|
||||
confidenceRationale:
|
||||
type: string
|
||||
minLength: 1
|
||||
- if:
|
||||
properties:
|
||||
classificationId:
|
||||
const: ui-light
|
||||
then:
|
||||
properties:
|
||||
targetLaneId:
|
||||
enum:
|
||||
- fast-feedback
|
||||
- confidence
|
||||
- if:
|
||||
properties:
|
||||
classificationId:
|
||||
const: ui-workflow
|
||||
then:
|
||||
properties:
|
||||
targetLaneId:
|
||||
enum:
|
||||
- confidence
|
||||
- heavy-governance
|
||||
- if:
|
||||
properties:
|
||||
classificationId:
|
||||
const: surface-guard
|
||||
then:
|
||||
properties:
|
||||
targetLaneId:
|
||||
const: heavy-governance
|
||||
- if:
|
||||
properties:
|
||||
classificationId:
|
||||
const: discovery-heavy
|
||||
then:
|
||||
properties:
|
||||
targetLaneId:
|
||||
const: heavy-governance
|
||||
- if:
|
||||
properties:
|
||||
classificationId:
|
||||
const: browser
|
||||
then:
|
||||
properties:
|
||||
targetLaneId:
|
||||
const: browser
|
||||
MixedFileResolution:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- filePath
|
||||
- primaryClassificationId
|
||||
- resolutionStrategy
|
||||
- rationale
|
||||
- followUpRequired
|
||||
properties:
|
||||
filePath:
|
||||
type: string
|
||||
minLength: 1
|
||||
primaryClassificationId:
|
||||
$ref: '#/components/schemas/ClassificationId'
|
||||
secondaryClassificationIds:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ClassificationId'
|
||||
resolutionStrategy:
|
||||
type: string
|
||||
enum:
|
||||
- split-file
|
||||
- broadest-cost-wins
|
||||
rationale:
|
||||
type: string
|
||||
minLength: 1
|
||||
followUpRequired:
|
||||
type: boolean
|
||||
LanePlacementRule:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- ruleId
|
||||
- classificationId
|
||||
- laneId
|
||||
- allowance
|
||||
- reason
|
||||
properties:
|
||||
ruleId:
|
||||
type: string
|
||||
classificationId:
|
||||
$ref: '#/components/schemas/ClassificationId'
|
||||
laneId:
|
||||
$ref: '#/components/schemas/LaneId'
|
||||
allowance:
|
||||
$ref: '#/components/schemas/PlacementAllowance'
|
||||
reason:
|
||||
type: string
|
||||
exceptionPolicy:
|
||||
type: string
|
||||
LanePlacementValidationRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- laneId
|
||||
properties:
|
||||
laneId:
|
||||
$ref: '#/components/schemas/LaneId'
|
||||
classificationId:
|
||||
$ref: '#/components/schemas/ClassificationId'
|
||||
familyId:
|
||||
type: string
|
||||
filePath:
|
||||
type: string
|
||||
observedCostSignals:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
anyOf:
|
||||
- required:
|
||||
- classificationId
|
||||
- required:
|
||||
- familyId
|
||||
- required:
|
||||
- filePath
|
||||
LanePlacementValidationResult:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- laneId
|
||||
- resolvedClassificationId
|
||||
- allowance
|
||||
- valid
|
||||
- reasons
|
||||
properties:
|
||||
laneId:
|
||||
$ref: '#/components/schemas/LaneId'
|
||||
resolvedClassificationId:
|
||||
$ref: '#/components/schemas/ClassificationId'
|
||||
familyId:
|
||||
type: string
|
||||
allowance:
|
||||
$ref: '#/components/schemas/PlacementAllowance'
|
||||
valid:
|
||||
type: boolean
|
||||
reasons:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
remediationOptions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
mixedFileResolution:
|
||||
$ref: '#/components/schemas/MixedFileResolution'
|
||||
HeavyBudgetEvaluation:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- targetType
|
||||
- targetId
|
||||
- thresholdSeconds
|
||||
- measuredSeconds
|
||||
- baselineSource
|
||||
- enforcement
|
||||
- lifecycleState
|
||||
- status
|
||||
properties:
|
||||
targetType:
|
||||
type: string
|
||||
enum:
|
||||
- lane
|
||||
- classification
|
||||
- family
|
||||
targetId:
|
||||
type: string
|
||||
thresholdSeconds:
|
||||
type: integer
|
||||
minimum: 1
|
||||
measuredSeconds:
|
||||
type: number
|
||||
minimum: 0
|
||||
baselineSource:
|
||||
$ref: '#/components/schemas/BudgetBaselineSource'
|
||||
enforcement:
|
||||
$ref: '#/components/schemas/BudgetEnforcement'
|
||||
lifecycleState:
|
||||
$ref: '#/components/schemas/BudgetLifecycleState'
|
||||
status:
|
||||
$ref: '#/components/schemas/BudgetStatus'
|
||||
HeavyAttributionReport:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- laneId
|
||||
- finishedAt
|
||||
- wallClockSeconds
|
||||
- slowestEntries
|
||||
- classificationAttribution
|
||||
- familyAttribution
|
||||
- budgetEvaluations
|
||||
- artifactDirectory
|
||||
properties:
|
||||
laneId:
|
||||
$ref: '#/components/schemas/LaneId'
|
||||
finishedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
wallClockSeconds:
|
||||
type: number
|
||||
minimum: 0
|
||||
slowestEntries:
|
||||
type: array
|
||||
description: Canonical top 10 runtime hotspot entries ordered by slowest wall-clock time.
|
||||
minItems: 10
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- label
|
||||
- wallClockSeconds
|
||||
properties:
|
||||
label:
|
||||
type: string
|
||||
wallClockSeconds:
|
||||
type: number
|
||||
minimum: 0
|
||||
classificationAttribution:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- classificationId
|
||||
- totalWallClockSeconds
|
||||
properties:
|
||||
classificationId:
|
||||
$ref: '#/components/schemas/ClassificationId'
|
||||
totalWallClockSeconds:
|
||||
type: number
|
||||
minimum: 0
|
||||
hotspotFiles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
familyAttribution:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- familyId
|
||||
- classificationId
|
||||
- totalWallClockSeconds
|
||||
properties:
|
||||
familyId:
|
||||
type: string
|
||||
classificationId:
|
||||
$ref: '#/components/schemas/ClassificationId'
|
||||
totalWallClockSeconds:
|
||||
type: number
|
||||
minimum: 0
|
||||
hotspotFiles:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
budgetEvaluations:
|
||||
type: array
|
||||
minItems: 1
|
||||
allOf:
|
||||
- contains:
|
||||
type: object
|
||||
required:
|
||||
- targetType
|
||||
- targetId
|
||||
properties:
|
||||
targetType:
|
||||
const: lane
|
||||
targetId:
|
||||
const: heavy-governance
|
||||
- contains:
|
||||
type: object
|
||||
required:
|
||||
- targetType
|
||||
properties:
|
||||
targetType:
|
||||
const: classification
|
||||
- contains:
|
||||
type: object
|
||||
required:
|
||||
- targetType
|
||||
properties:
|
||||
targetType:
|
||||
const: family
|
||||
items:
|
||||
$ref: '#/components/schemas/HeavyBudgetEvaluation'
|
||||
artifactDirectory:
|
||||
type: string
|
||||
const: storage/logs/test-lanes
|
||||
@ -0,0 +1,809 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://tenantatlas.local/schemas/heavy-test-classification.schema.json",
|
||||
"title": "HeavyTestClassificationCatalog",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"version",
|
||||
"artifactDirectory",
|
||||
"classifications",
|
||||
"families",
|
||||
"mixedFileResolutions",
|
||||
"placementRules",
|
||||
"driftGuards",
|
||||
"budgetTargets"
|
||||
],
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"artifactDirectory": {
|
||||
"type": "string",
|
||||
"const": "storage/logs/test-lanes"
|
||||
},
|
||||
"classifications": {
|
||||
"type": "array",
|
||||
"minItems": 5,
|
||||
"items": {
|
||||
"$ref": "#/$defs/classification"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"contains": {
|
||||
"type": "object",
|
||||
"required": ["classificationId"],
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "ui-light"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"contains": {
|
||||
"type": "object",
|
||||
"required": ["classificationId"],
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "ui-workflow"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"contains": {
|
||||
"type": "object",
|
||||
"required": ["classificationId"],
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "surface-guard"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"contains": {
|
||||
"type": "object",
|
||||
"required": ["classificationId"],
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "discovery-heavy"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"contains": {
|
||||
"type": "object",
|
||||
"required": ["classificationId"],
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "browser"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"families": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/family"
|
||||
}
|
||||
},
|
||||
"mixedFileResolutions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/mixedFileResolution"
|
||||
}
|
||||
},
|
||||
"placementRules": {
|
||||
"type": "array",
|
||||
"minItems": 5,
|
||||
"items": {
|
||||
"$ref": "#/$defs/placementRule"
|
||||
}
|
||||
},
|
||||
"driftGuards": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/driftGuard"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"contains": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"targetRefs"
|
||||
],
|
||||
"properties": {
|
||||
"targetRefs": {
|
||||
"type": "array",
|
||||
"contains": {
|
||||
"const": "browser"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"contains": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"targetRefs"
|
||||
],
|
||||
"properties": {
|
||||
"targetRefs": {
|
||||
"type": "array",
|
||||
"contains": {
|
||||
"const": "discovery-heavy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"contains": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"targetRefs"
|
||||
],
|
||||
"properties": {
|
||||
"targetRefs": {
|
||||
"type": "array",
|
||||
"contains": {
|
||||
"const": "surface-guard"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"budgetTargets": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/budgetTarget"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"contains": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"targetType",
|
||||
"targetId"
|
||||
],
|
||||
"properties": {
|
||||
"targetType": {
|
||||
"const": "lane"
|
||||
},
|
||||
"targetId": {
|
||||
"const": "heavy-governance"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"contains": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"targetType"
|
||||
],
|
||||
"properties": {
|
||||
"targetType": {
|
||||
"const": "classification"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"contains": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"targetType"
|
||||
],
|
||||
"properties": {
|
||||
"targetType": {
|
||||
"const": "family"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"classificationId": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ui-light",
|
||||
"ui-workflow",
|
||||
"surface-guard",
|
||||
"discovery-heavy",
|
||||
"browser"
|
||||
]
|
||||
},
|
||||
"laneId": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"fast-feedback",
|
||||
"confidence",
|
||||
"heavy-governance",
|
||||
"browser",
|
||||
"profiling",
|
||||
"junit"
|
||||
]
|
||||
},
|
||||
"selector": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"selectorType",
|
||||
"selectorValue",
|
||||
"selectorRole",
|
||||
"sourceOfTruth"
|
||||
],
|
||||
"properties": {
|
||||
"selectorType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"suite",
|
||||
"path",
|
||||
"group",
|
||||
"file"
|
||||
]
|
||||
},
|
||||
"selectorValue": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"selectorRole": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"include",
|
||||
"exclude",
|
||||
"inventory-only"
|
||||
]
|
||||
},
|
||||
"sourceOfTruth": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"manifest",
|
||||
"pest-group",
|
||||
"guard-test",
|
||||
"report-attribution"
|
||||
]
|
||||
},
|
||||
"rationale": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"classification": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"classificationId",
|
||||
"purpose",
|
||||
"dominantCostDrivers",
|
||||
"defaultLaneId",
|
||||
"allowedLaneIds",
|
||||
"forbiddenLaneIds",
|
||||
"reviewerSignals",
|
||||
"escalationTriggers"
|
||||
],
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"$ref": "#/$defs/classificationId"
|
||||
},
|
||||
"purpose": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"dominantCostDrivers": {
|
||||
"$ref": "#/$defs/nonEmptyStringArray"
|
||||
},
|
||||
"defaultLaneId": {
|
||||
"$ref": "#/$defs/laneId"
|
||||
},
|
||||
"allowedLaneIds": {
|
||||
"$ref": "#/$defs/nonEmptyLaneArray"
|
||||
},
|
||||
"forbiddenLaneIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/laneId"
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"reviewerSignals": {
|
||||
"$ref": "#/$defs/nonEmptyStringArray"
|
||||
},
|
||||
"escalationTriggers": {
|
||||
"$ref": "#/$defs/nonEmptyStringArray"
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "browser"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"defaultLaneId": {
|
||||
"const": "browser"
|
||||
},
|
||||
"allowedLaneIds": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"maxItems": 1,
|
||||
"items": {
|
||||
"const": "browser"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "ui-workflow"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"defaultLaneId": {
|
||||
"const": "confidence"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "surface-guard"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"defaultLaneId": {
|
||||
"const": "heavy-governance"
|
||||
},
|
||||
"forbiddenLaneIds": {
|
||||
"allOf": [
|
||||
{
|
||||
"contains": {
|
||||
"const": "fast-feedback"
|
||||
}
|
||||
},
|
||||
{
|
||||
"contains": {
|
||||
"const": "confidence"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "discovery-heavy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"defaultLaneId": {
|
||||
"const": "heavy-governance"
|
||||
},
|
||||
"forbiddenLaneIds": {
|
||||
"allOf": [
|
||||
{
|
||||
"contains": {
|
||||
"const": "fast-feedback"
|
||||
}
|
||||
},
|
||||
{
|
||||
"contains": {
|
||||
"const": "confidence"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"family": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"familyId",
|
||||
"classificationId",
|
||||
"purpose",
|
||||
"currentLaneId",
|
||||
"targetLaneId",
|
||||
"selectors",
|
||||
"hotspotFiles",
|
||||
"costSignals",
|
||||
"validationStatus"
|
||||
],
|
||||
"properties": {
|
||||
"familyId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
|
||||
},
|
||||
"classificationId": {
|
||||
"$ref": "#/$defs/classificationId"
|
||||
},
|
||||
"purpose": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"currentLaneId": {
|
||||
"$ref": "#/$defs/laneId"
|
||||
},
|
||||
"targetLaneId": {
|
||||
"$ref": "#/$defs/laneId"
|
||||
},
|
||||
"selectors": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/selector"
|
||||
}
|
||||
},
|
||||
"hotspotFiles": {
|
||||
"$ref": "#/$defs/nonEmptyStringArray"
|
||||
},
|
||||
"costSignals": {
|
||||
"$ref": "#/$defs/nonEmptyStringArray"
|
||||
},
|
||||
"confidenceRationale": {
|
||||
"type": "string"
|
||||
},
|
||||
"validationStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"seeded",
|
||||
"reviewed",
|
||||
"migrated",
|
||||
"guarded"
|
||||
]
|
||||
}
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"targetLaneId": {
|
||||
"const": "confidence"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"required": [
|
||||
"confidenceRationale"
|
||||
],
|
||||
"properties": {
|
||||
"confidenceRationale": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "ui-light"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"targetLaneId": {
|
||||
"enum": [
|
||||
"fast-feedback",
|
||||
"confidence"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "ui-workflow"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"targetLaneId": {
|
||||
"enum": [
|
||||
"confidence",
|
||||
"heavy-governance"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "surface-guard"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"targetLaneId": {
|
||||
"const": "heavy-governance"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "discovery-heavy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"targetLaneId": {
|
||||
"const": "heavy-governance"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"classificationId": {
|
||||
"const": "browser"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"targetLaneId": {
|
||||
"const": "browser"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"mixedFileResolution": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"filePath",
|
||||
"primaryClassificationId",
|
||||
"resolutionStrategy",
|
||||
"rationale",
|
||||
"followUpRequired"
|
||||
],
|
||||
"properties": {
|
||||
"filePath": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"primaryClassificationId": {
|
||||
"$ref": "#/$defs/classificationId"
|
||||
},
|
||||
"secondaryClassificationIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/classificationId"
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"resolutionStrategy": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"split-file",
|
||||
"broadest-cost-wins"
|
||||
]
|
||||
},
|
||||
"rationale": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"followUpRequired": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"placementRule": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"ruleId",
|
||||
"classificationId",
|
||||
"laneId",
|
||||
"allowance",
|
||||
"reason"
|
||||
],
|
||||
"properties": {
|
||||
"ruleId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
|
||||
},
|
||||
"classificationId": {
|
||||
"$ref": "#/$defs/classificationId"
|
||||
},
|
||||
"laneId": {
|
||||
"$ref": "#/$defs/laneId"
|
||||
},
|
||||
"allowance": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"required",
|
||||
"allowed",
|
||||
"discouraged",
|
||||
"forbidden"
|
||||
]
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"exceptionPolicy": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"driftGuard": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"guardId",
|
||||
"scope",
|
||||
"assertionType",
|
||||
"targetRefs",
|
||||
"owningTestPaths",
|
||||
"failureContract"
|
||||
],
|
||||
"properties": {
|
||||
"guardId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
|
||||
},
|
||||
"scope": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"classification",
|
||||
"family",
|
||||
"lane",
|
||||
"report"
|
||||
]
|
||||
},
|
||||
"assertionType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"forbidden-membership",
|
||||
"required-membership",
|
||||
"required-attribution",
|
||||
"required-selector",
|
||||
"browser-isolation"
|
||||
]
|
||||
},
|
||||
"targetRefs": {
|
||||
"$ref": "#/$defs/nonEmptyStringArray"
|
||||
},
|
||||
"owningTestPaths": {
|
||||
"$ref": "#/$defs/nonEmptyStringArray"
|
||||
},
|
||||
"failureContract": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"budgetTarget": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"budgetId",
|
||||
"targetType",
|
||||
"targetId",
|
||||
"thresholdSeconds",
|
||||
"baselineSource",
|
||||
"enforcement",
|
||||
"lifecycleState"
|
||||
],
|
||||
"properties": {
|
||||
"budgetId": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
|
||||
},
|
||||
"targetType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"lane",
|
||||
"classification",
|
||||
"family"
|
||||
]
|
||||
},
|
||||
"targetId": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"thresholdSeconds": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"baselineSource": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"measured-current-suite",
|
||||
"measured-lane",
|
||||
"measured-post-spec-207"
|
||||
]
|
||||
},
|
||||
"enforcement": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"report-only",
|
||||
"warn",
|
||||
"hard-fail"
|
||||
]
|
||||
},
|
||||
"lifecycleState": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"draft",
|
||||
"measured",
|
||||
"documented",
|
||||
"enforced"
|
||||
]
|
||||
},
|
||||
"reviewCadence": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nonEmptyStringArray": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"nonEmptyLaneArray": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/$defs/laneId"
|
||||
},
|
||||
"uniqueItems": true
|
||||
}
|
||||
}
|
||||
}
|
||||
206
specs/208-heavy-suite-segmentation/data-model.md
Normal file
206
specs/208-heavy-suite-segmentation/data-model.md
Normal file
@ -0,0 +1,206 @@
|
||||
# Data Model: Filament/Livewire Heavy Suite Segmentation
|
||||
|
||||
This feature does not introduce new runtime database tables. The data-model work formalizes repository-level governance objects that describe how heavy Filament, Livewire, surface, discovery, and browser families are classified, assigned to lanes, guarded against drift, and attributed in reporting. It builds directly on the existing Spec 206 lane manifest and Spec 207 fixture-cost outputs.
|
||||
|
||||
## 1. Heavy Test Classification
|
||||
|
||||
### Purpose
|
||||
|
||||
Represents a repository-defined cost and purpose class for a UI-related test family.
|
||||
|
||||
### Fields
|
||||
|
||||
- `classification_id`: stable identifier such as `ui-light`, `ui-workflow`, `surface-guard`, `discovery-heavy`, or `browser`
|
||||
- `purpose`: contributor-facing description of what this class is meant to cover
|
||||
- `dominant_cost_drivers`: list of characteristics such as `single-mount`, `multi-mount`, `relation-manager breadth`, `reflection`, `resource discovery`, `workflow fan-out`, or `real-browser interaction`
|
||||
- `default_lane_id`: primary execution lane for this class
|
||||
- `allowed_lane_ids`: lanes where the class may legitimately appear
|
||||
- `forbidden_lane_ids`: lanes where the class must not appear
|
||||
- `reviewer_signals`: short list of clues a reviewer should look for when classifying a new test
|
||||
- `escalation_triggers`: conditions that force a test out of a lighter class, such as broad discovery, wide reflection, or repeated multi-surface mounts
|
||||
|
||||
### Validation rules
|
||||
|
||||
- `browser` must allow only the `browser` lane.
|
||||
- `discovery-heavy` must forbid `fast-feedback` and `confidence`.
|
||||
- `surface-guard` must forbid `fast-feedback` and `confidence` and default to `heavy-governance`.
|
||||
- `ui-light` may appear in `fast-feedback` and `confidence` only when its dominant cost drivers remain localized.
|
||||
- `ui-workflow` defaults to `confidence` and may escalate to `heavy-governance` if its breadth becomes governance-wide.
|
||||
|
||||
## 2. Heavy Test Family
|
||||
|
||||
### Purpose
|
||||
|
||||
Represents a named cluster of related tests that share a heavy behavior pattern, hotspot files, and intended lane placement.
|
||||
|
||||
### Fields
|
||||
|
||||
- `family_id`: stable identifier for the family
|
||||
- `classification_id`: owning heavy class
|
||||
- `purpose`: why the family exists and what safety it provides
|
||||
- `current_lane_id`: the lane where the family currently runs
|
||||
- `target_lane_id`: the intended lane after segmentation
|
||||
- `selectors`: one or more selectors that identify the family in the manifest or guards
|
||||
- `hotspot_files`: representative or dominant files in the family
|
||||
- `cost_signals`: concrete evidence of heaviness such as reflection use, multi-page mounts, relation-manager breadth, concern-driven fixture graphs, or repeated action-surface assertions
|
||||
- `confidence_rationale`: explanation of why the family should remain in Confidence or move to Heavy Governance
|
||||
- `validation_status`: `seeded`, `reviewed`, `migrated`, or `guarded`
|
||||
|
||||
### Validation rules
|
||||
|
||||
- Every family must have at least one selector and one hotspot file.
|
||||
- `target_lane_id` must be compatible with the owning `classification_id`.
|
||||
- Families assigned to `fast-feedback` may not carry `discovery-heavy` or broad `surface-guard` signals.
|
||||
- A family with `classification_id = browser` must target the `browser` lane only.
|
||||
- Families assigned to `confidence` must include `confidence_rationale` explaining why the retained UI safety is worth the lane cost.
|
||||
|
||||
## 3. Heavy Family Selector
|
||||
|
||||
### Purpose
|
||||
|
||||
Represents the checked-in selector used to find a heavy family during lane execution and guard validation.
|
||||
|
||||
### Fields
|
||||
|
||||
- `selector_type`: `suite`, `path`, `group`, or `file`
|
||||
- `selector_value`: exact selector value used by the manifest or guard
|
||||
- `selector_role`: `include`, `exclude`, or `inventory-only`
|
||||
- `rationale`: why this selector is needed
|
||||
- `source_of_truth`: `manifest`, `pest-group`, `guard-test`, or `report-attribution`
|
||||
|
||||
### Validation rules
|
||||
|
||||
- Selectors used for execution must be representable in `TestLaneManifest`.
|
||||
- Families with more than one hotspot should not rely on a single ad-hoc file selector alone unless the file is intentionally the whole family.
|
||||
|
||||
## 4. Lane Placement Rule
|
||||
|
||||
### Purpose
|
||||
|
||||
Describes the allowed relationship between a heavy class and an execution lane.
|
||||
|
||||
### Fields
|
||||
|
||||
- `rule_id`: stable identifier
|
||||
- `classification_id`: heavy class under review
|
||||
- `lane_id`: execution lane
|
||||
- `allowance`: `required`, `allowed`, `discouraged`, or `forbidden`
|
||||
- `reason`: concise explanation of the rule
|
||||
- `exception_policy`: how rare justified exceptions are handled
|
||||
|
||||
### Validation rules
|
||||
|
||||
- Each heavy class must have exactly one `required` or `default` lane target.
|
||||
- `browser` cannot be `allowed` in non-browser lanes.
|
||||
- `discovery-heavy` and broad `surface-guard` classes must be `forbidden` in `fast-feedback`.
|
||||
- `confidence` may contain only the classes explicitly marked `allowed` or `required` there.
|
||||
|
||||
## 5. Mixed File Resolution
|
||||
|
||||
### Purpose
|
||||
|
||||
Represents how a file that exhibits more than one heavy behavior is resolved for lane placement.
|
||||
|
||||
### Fields
|
||||
|
||||
- `file_path`: relative test file path
|
||||
- `primary_classification_id`: the classification that drives lane placement
|
||||
- `secondary_classification_ids`: additional observed behaviors
|
||||
- `resolution_strategy`: `split-file` or `broadest-cost-wins`
|
||||
- `rationale`: why the chosen resolution is acceptable
|
||||
- `follow_up_required`: boolean
|
||||
|
||||
### Validation rules
|
||||
|
||||
- Files with `resolution_strategy = broadest-cost-wins` must have an explicit rationale.
|
||||
- Files whose primary classification is `discovery-heavy` or `surface-guard` may not remain in `fast-feedback` or `confidence`.
|
||||
|
||||
## 6. Drift Guard
|
||||
|
||||
### Purpose
|
||||
|
||||
Represents a checked-in validation that prevents wrong-lane placement for heavy classes or families.
|
||||
|
||||
### Fields
|
||||
|
||||
- `guard_id`: stable identifier
|
||||
- `scope`: `classification`, `family`, `lane`, or `report`
|
||||
- `assertion_type`: `forbidden-membership`, `required-membership`, `required-attribution`, `required-selector`, or `browser-isolation`
|
||||
- `target_refs`: classes, families, files, or lanes covered by the guard
|
||||
- `failure_contract`: the minimum actionable failure output expected from the guard
|
||||
- `owning_test_paths`: tests that enforce the guard
|
||||
|
||||
### Validation rules
|
||||
|
||||
- Every forbidden lane relationship for `browser`, `discovery-heavy`, and broad `surface-guard` must have at least one drift guard.
|
||||
- Drift guards must fail with actionable output that identifies the violating file or family.
|
||||
|
||||
## 7. Heavy Budget Threshold
|
||||
|
||||
### Purpose
|
||||
|
||||
Represents the runtime budget target for a heavy lane, heavy class, or heavy family.
|
||||
|
||||
### Fields
|
||||
|
||||
- `budget_id`: stable identifier
|
||||
- `target_type`: `lane`, `classification`, or `family`
|
||||
- `target_id`: referenced lane, classification, or family
|
||||
- `threshold_seconds`: allowed wall-clock target
|
||||
- `baseline_source`: `measured-current-suite`, `measured-lane`, or `measured-post-spec-207`
|
||||
- `enforcement_level`: `report-only`, `warn`, or `hard-fail`
|
||||
- `lifecycle_state`: `draft`, `measured`, `documented`, or `enforced`
|
||||
- `review_cadence`: how often the budget should be revisited
|
||||
|
||||
### Validation rules
|
||||
|
||||
- Heavy Governance must retain a lane-level budget.
|
||||
- At least one heavy family budget must exist beyond the lane total.
|
||||
- New family budgets should stay report-oriented until lane baselines stabilize.
|
||||
|
||||
## 8. Heavy Attribution Report
|
||||
|
||||
### Purpose
|
||||
|
||||
Represents the reporting view that explains heavy-lane cost by class and family.
|
||||
|
||||
### Fields
|
||||
|
||||
- `lane_id`: lane being reported
|
||||
- `wall_clock_seconds`: total lane duration
|
||||
- `slowest_entries`: canonical top 10 runtime hotspot entries ordered by slowest wall-clock time
|
||||
- `classification_attribution`: totals grouped by heavy class
|
||||
- `family_attribution`: totals grouped by heavy family
|
||||
- `budget_evaluations`: lane, classification, and family budget evaluations
|
||||
- `artifact_paths`: generated report artifacts
|
||||
|
||||
### Validation rules
|
||||
|
||||
- Heavy reports must retain the canonical top 10 runtime hotspot view ordered by slowest wall-clock time.
|
||||
- If a heavy family is cataloged, its cost should be attributable either directly or through its classification.
|
||||
- Report artifact paths must remain under the app-root contract path `storage/logs/test-lanes`.
|
||||
|
||||
## 9. First-Slice Governance Inventory
|
||||
|
||||
### Existing lane infrastructure
|
||||
|
||||
- Operational lanes already present: `fast-feedback`, `confidence`, `browser`, and `heavy-governance`
|
||||
- Support-lane entries already present: `profiling` and `junit`
|
||||
- Existing family budgets currently cover `ops-ux-governance` and `browser-smoke`
|
||||
|
||||
### Initial heavy family clusters
|
||||
|
||||
- `tests/Feature/Filament/`: broadest mixed UI cluster; currently contains UI-Light, UI-Workflow, Surface-Guard, and Discovery-Heavy candidates
|
||||
- `tests/Feature/Rbac/`: relation-manager and role-matrix heavy families with repeated Livewire mounts and capability transitions
|
||||
- `tests/Feature/Baselines/` plus related Filament baseline pages: dense workflow and matrix-style coverage with expensive fixture construction
|
||||
- `tests/Feature/Concerns/`: heavy builder traits such as baseline compare and portfolio triage fixtures that amplify test cost across families
|
||||
- `tests/Feature/Guards/ActionSurfaceContractTest.php` and related governance guards: canonical Surface-Guard seeds already treated as heavy
|
||||
- `tests/Browser/`: isolated Browser class, already a separate lane and cost domain
|
||||
|
||||
### Initial heavy signals
|
||||
|
||||
- Reflection-based discovery such as `ReflectionProperty` and global-search parity checks
|
||||
- Multi-mount Livewire tests across pages, relation managers, and actions
|
||||
- Broad action-surface and navigation-discipline validation
|
||||
- Concern-driven fixture construction that creates dense comparison or triage graphs
|
||||
- Browser interaction and end-to-end smoke coverage
|
||||
168
specs/208-heavy-suite-segmentation/plan.md
Normal file
168
specs/208-heavy-suite-segmentation/plan.md
Normal file
@ -0,0 +1,168 @@
|
||||
# Implementation Plan: Filament/Livewire Heavy Suite Segmentation
|
||||
|
||||
**Branch**: `208-heavy-suite-segmentation` | **Date**: 2026-04-16 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/208-heavy-suite-segmentation/spec.md`
|
||||
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/208-heavy-suite-segmentation/spec.md`
|
||||
|
||||
## Summary
|
||||
|
||||
Build Spec 208 on top of the existing Spec 206 lane manifest and the Spec 207 fixture-cost work by introducing a repo-local heavy UI classification catalog, inventorying the current Filament-, Livewire-, surface-, discovery-, and workflow-heavy families, tightening manifest selectors and Pest groups around those families, extending guard coverage so heavy tests cannot drift into the wrong lanes, and adding class- and family-level attribution to heavy-lane reporting without introducing new runtime services or product-facing behavior.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: PHP 8.4.15
|
||||
**Primary Dependencies**: Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail
|
||||
**Storage**: SQLite `:memory:` for the default test environment, existing lane artifacts under the app-root contract path `storage/logs/test-lanes`, and no new product persistence
|
||||
**Testing**: Pest unit, feature, browser, architecture, and guard suites run through Sail-wrapped `artisan test`; lane selection is currently controlled by `Tests\Support\TestLaneManifest`, Pest groups in `tests/Pest.php`, and focused guard tests under `tests/Feature/Guards`
|
||||
**Target Platform**: Laravel monorepo application in `apps/platform`, executed locally through Sail and later hardened in shared CI
|
||||
**Project Type**: Monorepo with a Laravel platform app and separate Astro website; this feature is scoped to platform test-governance infrastructure
|
||||
**Performance Goals**: Keep Fast Feedback free of Discovery-Heavy and broad Surface-Guard families, keep Confidence limited to documented UI-Light and selected UI-Workflow families, make Heavy Governance attributable by heavy class and family, and preserve or improve the post-Spec 207 fast-feedback and confidence baselines while keeping the heavy lane observable
|
||||
**Constraints**: Sail-first commands only; no new product routes, assets, runtime services, or dependencies; Browser remains a separate lane; the classification model must stay small and evidence-driven; directory names alone cannot be the source of truth; mixed files must be split or classified by their broadest cost driver
|
||||
**Scale/Scope**: Existing lane infrastructure already defines six checked-in lane entries; heavy families currently concentrate in `tests/Feature/Filament`, `tests/Feature/Rbac`, `tests/Feature/Baselines`, `tests/Feature/Concerns`, broad governance guards, and `tests/Browser`; the exploratory inventory identified roughly 209 Filament feature tests, about 35 RBAC-heavy files, and dozens of Baseline Compare and governance-adjacent tests that need explicit segmentation
|
||||
|
||||
### Filament v5 Implementation Notes
|
||||
|
||||
- **Livewire v4.0+ compliance**: Preserved. This feature changes only repository test-governance around Filament and Livewire surfaces, not runtime Filament or Livewire behavior.
|
||||
- **Provider registration location**: Unchanged. Existing panel providers remain registered in `bootstrap/providers.php`.
|
||||
- **Global search rule**: No globally searchable resources are added or modified. Tests may classify global-search parity checks as heavy discovery families, but runtime global-search behavior is unchanged.
|
||||
- **Destructive actions**: No runtime destructive actions are introduced. Any new tests added by this feature continue to validate existing confirmation and authorization behavior.
|
||||
- **Asset strategy**: No panel-only or shared assets are added. Existing `filament:assets` deployment behavior remains unchanged.
|
||||
- **Testing plan**: Add Pest guard coverage for heavy-classification catalog validity, lane-to-class mapping, heavy-family drift detection, report attribution, and targeted validation of the resegmented Fast Feedback, Confidence, and Heavy Governance lanes.
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- Inventory-first: PASS. No inventory, snapshot, or backup truth is changed.
|
||||
- Read/write separation: PASS. The feature only changes repository test-governance behavior and introduces no end-user mutation path.
|
||||
- Graph contract path: PASS. No Graph calls, contract-registry changes, or provider runtime integrations are added.
|
||||
- Deterministic capabilities: PASS. No capability resolver or authorization registry changes.
|
||||
- RBAC-UX, workspace isolation, tenant isolation: PASS. No runtime routes, policies, global search availability, or tenant/workspace enforcement semantics are changed.
|
||||
- Run observability and Ops-UX: PASS. Reporting remains filesystem-based through the existing lane tooling and does not introduce `OperationRun` behavior.
|
||||
- Data minimization: PASS. Heavy-family inventories and lane reports remain repo-local and contain no secrets or customer payloads.
|
||||
- Proportionality and bloat control: PASS WITH LIMITS. The only new semantic layer is a narrow repo-local classification catalog for heavy UI test families. The plan explicitly avoids a broader meta-framework and keeps lane placement tied to measurable cost and purpose.
|
||||
- TEST-TRUTH-001: PASS. The feature increases suite honesty by making broad discovery, surface, and workflow costs visible and governable instead of leaving them hidden in general feature lanes.
|
||||
- Filament/UI constitutions: PASS / NOT APPLICABLE. No operator-facing UI, action-surface runtime contract, badge semantics, or panel IA is changed.
|
||||
|
||||
**Phase 0 Gate Result**: PASS
|
||||
|
||||
- The feature remains bounded to repository test governance, reporting, selector rules, and author guidance.
|
||||
- No new runtime persistence, product routes, panels, assets, or Graph seams are introduced.
|
||||
- The chosen approach extends the existing Spec 206/207 tooling instead of creating a second test-governance system.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/208-heavy-suite-segmentation/
|
||||
├── plan.md
|
||||
├── research.md
|
||||
├── data-model.md
|
||||
├── quickstart.md
|
||||
├── contracts/
|
||||
│ ├── heavy-test-classification.schema.json
|
||||
│ └── heavy-suite-segmentation.logical.openapi.yaml
|
||||
└── tasks.md
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```text
|
||||
apps/
|
||||
├── platform/
|
||||
│ ├── composer.json
|
||||
│ ├── tests/
|
||||
│ │ ├── Pest.php
|
||||
│ │ ├── Support/
|
||||
│ │ │ ├── TestLaneManifest.php
|
||||
│ │ │ ├── TestLaneBudget.php
|
||||
│ │ │ └── TestLaneReport.php
|
||||
│ │ ├── Feature/
|
||||
│ │ │ ├── Baselines/
|
||||
│ │ │ ├── Concerns/
|
||||
│ │ │ ├── Filament/
|
||||
│ │ │ ├── Guards/
|
||||
│ │ │ ├── Navigation/
|
||||
│ │ │ ├── Onboarding/
|
||||
│ │ │ └── Rbac/
|
||||
│ │ ├── Browser/
|
||||
│ │ └── Unit/
|
||||
│ └── storage/logs/test-lanes/
|
||||
├── website/
|
||||
└── ...
|
||||
scripts/
|
||||
├── platform-test-lane
|
||||
└── platform-test-report
|
||||
```
|
||||
|
||||
**Structure Decision**: Keep implementation concentrated in the existing platform test-governance seams: `apps/platform/tests/Pest.php` for group tagging, `apps/platform/tests/Support/TestLaneManifest.php` for lane catalog and family metadata, `apps/platform/tests/Support/TestLaneReport.php` for attribution output, targeted guard tests under `apps/platform/tests/Feature/Guards`, and selective heavy-family files under `apps/platform/tests/Feature/Filament`, `apps/platform/tests/Feature/Rbac`, `apps/platform/tests/Feature/Baselines`, and `apps/platform/tests/Feature/Concerns`. Planning artifacts stay inside `specs/208-heavy-suite-segmentation`.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| None | Not applicable | Not applicable |
|
||||
|
||||
## Proportionality Review
|
||||
|
||||
- **Current operator problem**: Contributors and reviewers cannot reliably see when a Filament or Livewire test family is too broad for Fast Feedback or Confidence, so expensive discovery and surface-guard behavior silently erodes faster lanes.
|
||||
- **Existing structure is insufficient because**: The current lane manifest distinguishes fast, confidence, browser, and heavy-governance at a coarse level but does not classify heavy UI families by their actual cost drivers or preserve their intent for reviewers.
|
||||
- **Narrowest correct implementation**: Extend the existing lane manifest, Pest group seams, guard tests, and report attribution with a small heavy-family catalog and class model instead of adding a new runner, new persistence, or a broad test taxonomy framework.
|
||||
- **Ownership cost created**: The repo must maintain the heavy classification catalog, targeted family inventory, lane-to-class rules, drift guards, and budget attribution as new UI-heavy tests are added.
|
||||
- **Alternative intentionally rejected**: Purely directory-based moves or ad-hoc per-file exclusions, because they hide cost instead of making it reviewable and enforceable.
|
||||
- **Release truth**: Current-release repository truth and the direct next step after Specs 206 and 207.
|
||||
|
||||
## Phase 0 — Research (complete)
|
||||
|
||||
- Output: [research.md](./research.md)
|
||||
- Resolved key decisions:
|
||||
- Reuse the existing `TestLaneManifest`, `TestLaneBudget`, and `TestLaneReport` seams instead of introducing a new lane engine.
|
||||
- Model heavy UI cost with five explicit classes: `ui-light`, `ui-workflow`, `surface-guard`, `discovery-heavy`, and `browser`.
|
||||
- Keep `heavy-governance` as the operational destination for broad Surface-Guard and Discovery-Heavy families instead of adding a brand-new operational lane.
|
||||
- Represent heavy-family ownership through a hybrid of manifest selectors, granular Pest groups, and explicit hotspot file inventory rather than forcing a large directory move before the classification model stabilizes.
|
||||
- Treat mixed files as a first-class case: either split them or classify them by the broadest cost-driving behavior.
|
||||
- Start the heavy-family inventory from existing hotspots in `tests/Feature/Filament`, `tests/Feature/Rbac`, `tests/Feature/Baselines`, `tests/Feature/Concerns`, and the existing Browser lane, then tighten placement from profiling evidence.
|
||||
- Extend drift guards from coarse lane exclusion to explicit class-to-lane validation so new heavy tests cannot silently diffuse into Fast Feedback or Confidence.
|
||||
- Extend report output and family budgets to attribute heavy costs by class and family, not only by lane.
|
||||
|
||||
## Phase 1 — Design & Contracts (complete)
|
||||
|
||||
- Output: [data-model.md](./data-model.md) formalizes the heavy classification catalog, heavy family inventory, lane placement rules, mixed-file handling, drift guards, and report attribution model.
|
||||
- Output: [contracts/heavy-test-classification.schema.json](./contracts/heavy-test-classification.schema.json) defines the checked-in schema for the heavy classification catalog, family inventory, confidence rationale requirements, and mixed-file resolution records.
|
||||
- Output: [contracts/heavy-suite-segmentation.logical.openapi.yaml](./contracts/heavy-suite-segmentation.logical.openapi.yaml) captures the logical contract for classifying heavy families, reading family and mixed-file inventories, validating lane placement, and reading heavy-attribution reports.
|
||||
- Output: [quickstart.md](./quickstart.md) provides the rollout order, validation commands, and review checkpoints for the segmentation work.
|
||||
|
||||
### Post-design Constitution Re-check
|
||||
|
||||
- PASS: No runtime routes, panels, authorization planes, or Graph seams are introduced.
|
||||
- PASS: The new classification catalog is repo-local, directly justified by current suite cost, and bounded to existing lane-governance infrastructure.
|
||||
- PASS: The design prefers extending existing manifest, guard, and reporting seams over adding a second governance framework.
|
||||
- PASS WITH WORK: The heavy-family inventory must remain evidence-driven and limited to families that materially change lane cost or review behavior.
|
||||
- PASS WITH WORK: Confidence preservation must be documented explicitly as families move so the faster lane mix does not become a hollow smoke-only layer.
|
||||
|
||||
## Phase 2 — Implementation Planning
|
||||
|
||||
`tasks.md` should cover:
|
||||
|
||||
- Inventorying the current heavy Filament, Livewire, surface, discovery, workflow, wizard, header-action, and navigation-discipline families touched by current lanes.
|
||||
- Extending `TestLaneManifest` with a checked-in heavy-classification catalog and explicit heavy-family inventory.
|
||||
- Adding granular Pest groups or equivalent checked-in metadata for `ui-light`, `ui-workflow`, `surface-guard`, and `discovery-heavy` where group-based selection provides better drift control.
|
||||
- Refining Fast Feedback and Confidence selectors so Discovery-Heavy and broad Surface-Guard families are excluded intentionally rather than by scattered file exceptions.
|
||||
- Reassigning the first heavy hotspot families from coarse exclusions into explicit `heavy-governance` family ownership, including action-surface, header-action, navigation-discipline, relation-manager, wizard, and discovery-heavy hotspots.
|
||||
- Extending `TestLaneBudget` and `TestLaneReport` so heavy budgets and slowest entries are attributable by class and family, not only by lane.
|
||||
- Adding or expanding guard tests that verify class-to-lane rules, browser isolation, mixed-family handling, and heavy-family drift detection.
|
||||
- Validating that Confidence still contains the intended UI-Light and selected UI-Workflow safety after the moves.
|
||||
- Running the existing lane wrappers to compare Fast Feedback, Confidence, and Heavy Governance behavior against the post-Spec 207 baselines.
|
||||
- Publishing concise author and reviewer guidance for new heavy UI tests so placement intent is visible at review time.
|
||||
|
||||
### Contract Implementation Note
|
||||
|
||||
- The JSON schema is schema-first and repository-tooling-oriented. It defines what the checked-in heavy classification catalog, family inventory, confidence rationale, and mixed-file resolution records must express even if the first implementation remains PHP arrays in `TestLaneManifest`.
|
||||
- The OpenAPI file is logical rather than transport-prescriptive. It documents the expected semantics of heavy-family classification, mixed-file resolution, lane-validation, and report-attribution flows for in-process repository tooling.
|
||||
- The plan intentionally avoids introducing a new runtime service, a new database table, or a new artifact root outside the existing `storage/logs/test-lanes` contract.
|
||||
|
||||
### Deployment Sequencing Note
|
||||
|
||||
- No database migration is planned.
|
||||
- No asset publish step changes.
|
||||
- The rollout should start with heavy-family inventory and classification rules, then tighten selectors and guard tests, then extend heavy-attribution reporting, and finally validate the affected lanes against the existing baseline and budget seams.
|
||||
139
specs/208-heavy-suite-segmentation/quickstart.md
Normal file
139
specs/208-heavy-suite-segmentation/quickstart.md
Normal file
@ -0,0 +1,139 @@
|
||||
# Quickstart: Filament/Livewire Heavy Suite Segmentation
|
||||
|
||||
## Goal
|
||||
|
||||
Segment the current heavy Filament, Livewire, surface, discovery, and browser families so Fast Feedback stays lean, Confidence retains meaningful UI trust, Heavy Governance becomes the deliberate home for broad governance scans, and heavy cost is attributable by class and family rather than only by lane total.
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. Inventory the current heavy families already visible in `tests/Feature/Filament`, `tests/Feature/Rbac`, `tests/Feature/Baselines`, `tests/Feature/Concerns`, and `tests/Browser`.
|
||||
2. Finalize the checked-in heavy class catalog: `ui-light`, `ui-workflow`, `surface-guard`, `discovery-heavy`, and `browser`.
|
||||
3. Extend `TestLaneManifest` with the heavy-classification catalog, explicit heavy-family inventory, and budget targets for the first seeded heavy families.
|
||||
4. Add or refine Pest groups and manifest selectors so the seeded heavy families are technically separable without broad directory churn.
|
||||
5. Move the broadest Surface-Guard and Discovery-Heavy families out of Fast Feedback and Confidence into explicit `heavy-governance` ownership.
|
||||
6. Add or expand guard tests so class-to-lane drift fails clearly for Browser, Discovery-Heavy, and broad Surface-Guard families.
|
||||
7. Extend `TestLaneReport` so heavy-lane output attributes cost by heavy class and named family.
|
||||
8. Validate Fast Feedback, Confidence, and Heavy Governance against the existing post-Spec 207 baselines and current heavy-lane thresholds.
|
||||
9. Publish concise author and reviewer guidance explaining how to classify new heavy UI tests.
|
||||
|
||||
## Suggested Code Touches
|
||||
|
||||
```text
|
||||
apps/platform/composer.json
|
||||
apps/platform/tests/Pest.php
|
||||
apps/platform/tests/Support/TestLaneManifest.php
|
||||
apps/platform/tests/Support/TestLaneBudget.php
|
||||
apps/platform/tests/Support/TestLaneReport.php
|
||||
apps/platform/tests/Feature/Guards/*
|
||||
apps/platform/tests/Feature/Filament/*
|
||||
apps/platform/tests/Feature/Rbac/*
|
||||
apps/platform/tests/Feature/Baselines/*
|
||||
apps/platform/tests/Feature/Concerns/*
|
||||
apps/platform/tests/Browser/*
|
||||
scripts/platform-test-lane
|
||||
scripts/platform-test-report
|
||||
```
|
||||
|
||||
## Validation Flow
|
||||
|
||||
Validate the segmented end-state through the existing checked-in lane wrappers first:
|
||||
|
||||
```bash
|
||||
./scripts/platform-test-lane fast-feedback
|
||||
./scripts/platform-test-lane confidence
|
||||
./scripts/platform-test-lane heavy-governance
|
||||
./scripts/platform-test-lane browser
|
||||
./scripts/platform-test-lane profiling
|
||||
./scripts/platform-test-report fast-feedback
|
||||
./scripts/platform-test-report confidence
|
||||
./scripts/platform-test-report heavy-governance
|
||||
./scripts/platform-test-report profiling
|
||||
cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent
|
||||
```
|
||||
|
||||
Keep the implementation loop tight with the most relevant focused suites first:
|
||||
|
||||
```bash
|
||||
cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards
|
||||
cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament --filter=PolicyResourceAdminSearchParityTest
|
||||
cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Baselines --filter=BaselineCompareMatrixPageTest
|
||||
cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Rbac --filter=UiEnforcement
|
||||
cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser
|
||||
```
|
||||
|
||||
## Recorded Baselines
|
||||
|
||||
Use the existing Spec 206/207 evidence as the starting comparison point for this rollout.
|
||||
|
||||
| Scope | Current reference | Budget / note |
|
||||
|-------|-------------------|---------------|
|
||||
| `fast-feedback` | `176.74s` post-Spec 207 comparison baseline | Must stay stable or improve while excluding Discovery-Heavy and broad Surface-Guard families |
|
||||
| `confidence` | `394.38s` post-Spec 207 comparison baseline | Must stay stable or improve while retaining documented UI-Light and selected UI-Workflow coverage |
|
||||
| `heavy-governance` | Existing documented heavy-lane threshold `300s`; refresh measured baseline during rollout | Needs updated family attribution after segmentation |
|
||||
| `browser` | Existing isolated lane with dedicated budget | Remains separate and must not diffuse into other lanes |
|
||||
|
||||
## Classification Rules
|
||||
|
||||
- `ui-light`: localized component, page, or narrow action tests with limited mounts, limited reflection, and no broad discovery.
|
||||
- `ui-workflow`: bounded multi-step or multi-surface workflows that still represent real product trust and normally belong in Confidence.
|
||||
- `surface-guard`: broad action-surface, header-action, navigation-discipline, or relation-manager governance tests whose breadth makes them intentional heavy checks.
|
||||
- `discovery-heavy`: tests that scan resources, pages, relation managers, global-search behavior, or reflection-heavy registries across a broad surface.
|
||||
- `browser`: real end-to-end browser interaction and smoke coverage; always separate.
|
||||
|
||||
## Seeded Family Examples
|
||||
|
||||
Use the manifest catalog as the source of truth. The first checked-in families should stay aligned with these examples:
|
||||
|
||||
| Family | Classification | Target lane | Representative hotspots |
|
||||
|-------|----------------|-------------|--------------------------|
|
||||
| `backup-set-admin-tenant-parity` | `ui-light` | `confidence` | `tests/Feature/Filament/BackupSetAdminTenantParityTest.php` |
|
||||
| `baseline-compare-matrix-workflow` | `ui-workflow` | `confidence` | `tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php`, `tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php` |
|
||||
| `onboarding-wizard-enforcement` | `ui-workflow` | `confidence` | `tests/Feature/Rbac/OnboardingWizardUiEnforcementTest.php` |
|
||||
| `finding-bulk-actions-workflow` | `ui-workflow` | `heavy-governance` | `tests/Feature/Findings/FindingBulkActionsTest.php` |
|
||||
| `findings-workflow-surfaces` | `ui-workflow` | `heavy-governance` | `tests/Feature/Findings/FindingWorkflowRowActionsTest.php`, `tests/Feature/Findings/FindingWorkflowViewActionsTest.php`, `tests/Feature/Findings/FindingsListFiltersTest.php`, `tests/Feature/Findings/FindingExceptionRenewalTest.php` |
|
||||
| `drift-bulk-triage-all-matching` | `ui-workflow` | `heavy-governance` | `tests/Feature/Drift/DriftBulkAcknowledgeAllMatchingConfirmationTest.php` |
|
||||
| `baseline-profile-start-surfaces` | `ui-workflow` | `heavy-governance` | `tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php`, `tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php`, `tests/Feature/Filament/BaselineActionAuthorizationTest.php` |
|
||||
| `workspace-settings-slice-management` | `ui-workflow` | `heavy-governance` | `tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php` |
|
||||
| `workspace-only-admin-surface-independence` | `surface-guard` | `heavy-governance` | `tests/Feature/Filament/WorkspaceOnlySurfaceTenantIndependenceTest.php` |
|
||||
| `action-surface-contract` | `surface-guard` | `heavy-governance` | `tests/Feature/Guards/ActionSurfaceContractTest.php` |
|
||||
| `backup-items-relation-manager-enforcement` | `surface-guard` | `heavy-governance` | `tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php` |
|
||||
| `workspace-memberships-relation-manager-enforcement` | `surface-guard` | `heavy-governance` | `tests/Feature/Rbac/WorkspaceMembershipsRelationManagerUiEnforcementTest.php` |
|
||||
| `tenant-review-header-discipline` | `surface-guard` | `heavy-governance` | `tests/Feature/Filament/TenantReviewHeaderDisciplineTest.php` |
|
||||
| `panel-navigation-segregation` | `surface-guard` | `heavy-governance` | `tests/Feature/Filament/PanelNavigationSegregationTest.php` |
|
||||
| `ops-ux-governance` | `surface-guard` | `heavy-governance` | `tests/Feature/Filament/Alerts/AlertsKpiHeaderTest.php`, `tests/Feature/Guards/OperationLifecycleOpsUxGuardTest.php`, `tests/Feature/ProviderConnections/CredentialLeakGuardTest.php` |
|
||||
| `policy-resource-admin-search-parity` | `discovery-heavy` | `heavy-governance` | `tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php` |
|
||||
| `policy-version-admin-search-parity` | `discovery-heavy` | `heavy-governance` | `tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php` |
|
||||
| `browser-smoke` | `browser` | `browser` | `tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php` |
|
||||
|
||||
Mixed-file fallback remains explicit for `PolicyVersionAdminSearchParityTest.php`, `BaselineCompareMatrixBuilderTest.php`, `BuildsBaselineCompareMatrixFixtures.php`, and `BuildsPortfolioTriageFixtures.php`; reviewers should treat the manifest record as authoritative until those files are further split.
|
||||
|
||||
## Reviewer Guidance
|
||||
|
||||
1. Default new localized Filament assertions to `ui-light` only when the test stays scoped to one page or action surface and does not enumerate broad registries.
|
||||
2. Keep a test in `confidence` only if the manifest can justify it as `ui-light` or selected `ui-workflow` with a clear confidence rationale.
|
||||
3. Move any test that scans resources, global search, relation-manager registries, or broad action inventories into `discovery-heavy` or `surface-guard` even if it still lives under `tests/Feature/Filament`.
|
||||
4. Treat relation-manager enforcement, header-discipline, navigation-discipline, and action-surface governance as `heavy-governance` by default unless the scope is materially narrowed.
|
||||
5. When a file mixes confidence-worthy workflow checks and heavier fixture or discovery setup, record the dominant cost driver in the manifest and let the guard output explain the fallback.
|
||||
6. Keep browser smoke and end-to-end interaction isolated in the `browser` class and lane; no browser file should be justified into Fast Feedback or Confidence.
|
||||
7. Review heavy attribution reports by family first, then by classification, so lane-total regressions are traced back to a named hotspot family instead of a generic heavy bucket.
|
||||
|
||||
## Manual Review Checklist
|
||||
|
||||
1. Confirm every seeded heavy family has a checked-in class, current lane, target lane, selectors, and hotspot files.
|
||||
2. Confirm Fast Feedback contains no Discovery-Heavy families and no broad Surface-Guard families.
|
||||
3. Confirm Confidence retains only documented `ui-light` families and explicitly selected `ui-workflow` families.
|
||||
4. Confirm Browser remains isolated by suite, group, and guard tests.
|
||||
5. Confirm mixed files are either split or explicitly classified by their broadest cost driver.
|
||||
6. Confirm heavy-lane reporting attributes slowest cost by heavy class and family, not only by lane total.
|
||||
7. Confirm new drift guards fail with actionable output that names the violating file or family.
|
||||
8. Confirm author guidance makes it obvious when a new test is too heavy for Fast Feedback or Confidence.
|
||||
|
||||
## Exit Criteria
|
||||
|
||||
1. The repository has a checked-in heavy classification catalog and seeded heavy family inventory.
|
||||
2. Fast Feedback and Confidence exclude the families they are no longer supposed to carry.
|
||||
3. Heavy Governance owns the broad Surface-Guard and Discovery-Heavy families targeted by the rollout.
|
||||
4. Browser remains a separate class and lane.
|
||||
5. Drift guards prevent wrong-lane placement for Browser and seeded heavy governance families.
|
||||
6. Heavy-lane reporting exposes class- and family-level attribution.
|
||||
7. Fast Feedback and Confidence remain stable or improved against the recorded post-Spec 207 baselines.
|
||||
82
specs/208-heavy-suite-segmentation/research.md
Normal file
82
specs/208-heavy-suite-segmentation/research.md
Normal file
@ -0,0 +1,82 @@
|
||||
# Research: Filament/Livewire Heavy Suite Segmentation
|
||||
|
||||
## Decision 1: Reuse the existing lane governance infrastructure instead of adding a new runner layer
|
||||
|
||||
- Decision: Spec 208 should extend the current `TestLaneManifest`, `TestLaneBudget`, `TestLaneReport`, Composer lane commands, and repo-root wrapper scripts rather than introducing a second classification or execution framework.
|
||||
- Rationale: Spec 206 already established canonical lane entry points, artifact generation, budget reporting, and guard tests. Spec 208's problem is semantic classification and drift control inside that existing system, not missing execution plumbing.
|
||||
- Alternatives considered:
|
||||
- Create a second heavy-suite manifest or dedicated classification runner: rejected because it would duplicate the existing lane contract and create a parallel maintenance burden.
|
||||
- Perform only local per-file exclusions: rejected because the problem is repository-wide lane drift, not a single-file exception.
|
||||
|
||||
## Decision 2: Use a five-class heavy UI taxonomy tied to cost and purpose
|
||||
|
||||
- Decision: The heavy segmentation model should use exactly five classes for the first slice: `ui-light`, `ui-workflow`, `surface-guard`, `discovery-heavy`, and `browser`.
|
||||
- Rationale: The spec needs enough vocabulary to distinguish localized component checks from broad discovery scans and governance-wide surface guards, but not so much taxonomy that authors and reviewers stop applying it consistently.
|
||||
- Alternatives considered:
|
||||
- Reuse only the existing lane names: rejected because `fast-feedback`, `confidence`, and `heavy-governance` are execution targets, not sufficient descriptions of test character.
|
||||
- Create many sub-classes for every Filament test style: rejected because that would overfit current files and violate the proportionality constraint.
|
||||
|
||||
## Decision 3: Keep `heavy-governance` as the heavy operational lane instead of introducing a new fifth runtime lane
|
||||
|
||||
- Decision: Surface-Guard and Discovery-Heavy families should flow into the existing `heavy-governance` lane, with `browser` remaining separate.
|
||||
- Rationale: The repository already has a checked-in heavy lane with command wiring, artifacts, budgets, and guard coverage. The missing piece is better segmentation inside that lane, not another operational lane.
|
||||
- Alternatives considered:
|
||||
- Introduce a separate `heavy-ui` operational lane: rejected because the current problem can be solved by better family attribution inside the existing heavy lane.
|
||||
- Merge Browser into heavy governance: rejected because browser is already a distinct cost class with stronger isolation and different runtime semantics.
|
||||
|
||||
## Decision 4: Represent heavy-family ownership through a hybrid of manifest selectors, Pest groups, and explicit hotspot inventory
|
||||
|
||||
- Decision: The source of truth for heavy families should remain hybrid: manifest selectors for lane execution, Pest groups where group-level drift control helps, and an explicit checked-in family inventory for reviewer visibility.
|
||||
- Rationale: The current suite already uses all three seams in different ways. A hybrid model gives Spec 208 enough precision to isolate heavy families now without forcing a disruptive directory-first rewrite.
|
||||
- Alternatives considered:
|
||||
- Directory-only classification: rejected because mixed files and scattered hotspot tests would remain opaque.
|
||||
- Group-only classification: rejected because many heavy families are not yet grouped, and relying on groups alone would delay adoption.
|
||||
- File-list-only classification: rejected because it does not scale as families grow.
|
||||
|
||||
## Decision 5: Treat mixed files as a first-class segmentation problem
|
||||
|
||||
- Decision: When one file combines multiple cost patterns, the repository should either split the file or classify it by the broadest cost-driving behavior.
|
||||
- Rationale: Several current families mix localized assertions with discovery or surface-wide checks. Directory folklore cannot resolve those cases reliably, and reviewers need an explicit rule.
|
||||
- Alternatives considered:
|
||||
- Let mixed files stay where they are until they become too slow: rejected because the spec is about preventing lane drift before runtime erosion becomes normal.
|
||||
- Always force a split: rejected because some files may be readable enough if their broadest cost driver is explicit and guarded.
|
||||
|
||||
## Decision 6: Seed the heavy-family inventory from the current hotspot clusters rather than attempting a full-suite rewrite
|
||||
|
||||
- Decision: The first inventory should focus on the current heavy clusters that already distort lane cost: broad `tests/Feature/Filament` families, action-surface and header-action discipline tests, navigation-discipline tests, RBAC relation-manager and wizard UI-enforcement families, Baseline Compare feature and Filament pages, concern-based fixture builders, and the existing Browser suite.
|
||||
- Rationale: Exploratory repository analysis found these clusters repeatedly combining multi-mount Livewire tests, relation-manager breadth, wizard step flows, reflection-based discovery, header-action and navigation-discipline checks, broad action-surface validation, and expensive fixture construction.
|
||||
- Alternatives considered:
|
||||
- Classify every Feature test in one pass: rejected because it would add excessive churn before the taxonomy is proven.
|
||||
- Rely only on profiling output without a starting inventory: rejected because the current hotspots are already visible enough to justify an initial catalog.
|
||||
|
||||
## Decision 7: Extend drift guards from coarse lane isolation to explicit class-to-lane validation
|
||||
|
||||
- Decision: Spec 208 should add or expand guard tests so they validate heavy classification membership, lane compatibility, and wrong-lane drift for `browser`, `surface-guard`, and `discovery-heavy` families.
|
||||
- Rationale: The repository already has coarse guards for browser isolation and initial heavy-governance placement. Spec 208 needs those guards to become semantic instead of only path-based.
|
||||
- Alternatives considered:
|
||||
- Rely on documentation only: rejected because lane drift is a regression problem and must fail automatically.
|
||||
- Rely only on manual review: rejected because the suite surface is already large enough that inconsistent review would reintroduce drift quickly.
|
||||
|
||||
## Decision 8: Attribute heavy cost by class and family in reporting, not just by lane
|
||||
|
||||
- Decision: `TestLaneReport` and the family budget contract should be extended so heavy cost can be reported by heavy class and named family, not only by lane total.
|
||||
- Rationale: Once heavy families move out of faster lanes, maintainers still need to know whether drift is coming from discovery-heavy scans, surface-guard breadth, or workflow-heavy multi-mount tests.
|
||||
- Alternatives considered:
|
||||
- Keep lane-only reporting: rejected because lane totals alone cannot explain which heavy family is growing.
|
||||
- Add only per-file slowest output: rejected because file-level output lacks the semantic grouping reviewers need for governance decisions.
|
||||
|
||||
## Decision 9: Preserve Confidence with documented UI-Light and selected UI-Workflow coverage
|
||||
|
||||
- Decision: Confidence should explicitly retain localized `ui-light` coverage and a curated subset of `ui-workflow` tests, while Discovery-Heavy and broad Surface-Guard families move to `heavy-governance`.
|
||||
- Rationale: The spec explicitly rejects hollowing out Confidence. The lane must still carry meaningful UI safety even as the broadest heavy governance families are removed.
|
||||
- Alternatives considered:
|
||||
- Move nearly all Filament and Livewire tests to Heavy Governance: rejected because it would turn Confidence into a weak smoke-only lane.
|
||||
- Leave all current UI-heavy families in Confidence: rejected because that would fail the spec's fast-lane preservation goal.
|
||||
|
||||
## Decision 10: Keep the classification catalog repo-local and current-release only
|
||||
|
||||
- Decision: The classification catalog should stay inside repository planning and test-governance seams, with no new product persistence, runtime service, or cross-domain application taxonomy.
|
||||
- Rationale: The problem is local to test-suite architecture and developer feedback loops. Anything broader would violate PROP-001 and import unnecessary maintenance cost.
|
||||
- Alternatives considered:
|
||||
- Build a generic testing metadata system for future features: rejected because the current need is narrower and already satisfied by the manifest-plus-guards model.
|
||||
- Persist test family metadata outside the repo: rejected because the information is build-time governance data, not product truth.
|
||||
303
specs/208-heavy-suite-segmentation/spec.md
Normal file
303
specs/208-heavy-suite-segmentation/spec.md
Normal file
@ -0,0 +1,303 @@
|
||||
# Feature Specification: Filament/Livewire Heavy Suite Segmentation
|
||||
|
||||
**Feature Branch**: `208-heavy-suite-segmentation`
|
||||
**Created**: 2026-04-16
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Spec 208 - Filament/Livewire Heavy Suite Segmentation"
|
||||
|
||||
## Spec Candidate Check *(mandatory — SPEC-GATE-001)*
|
||||
|
||||
- **Problem**: TenantPilot's growing Filament-, Livewire-, surface-, discovery-, and governance-heavy test families are still mixed too closely with faster feedback paths, so the suite pays heavy UI and discovery cost in lanes that should stay lean.
|
||||
- **Today's failure**: Broad surface guards, discovery-heavy checks, and multi-mount workflow tests can drift into Fast Feedback or Confidence lanes without a clear classification model, making runtime slower while hiding which families are responsible.
|
||||
- **User-visible improvement**: Contributors get faster and more predictable standard lanes, maintainers can see heavy UI and discovery families as their own cost class, and reviewers can place new heavy tests correctly without relying on local tribal knowledge.
|
||||
- **Smallest enterprise-capable version**: Inventory the heavy Filament or Livewire families, define a small classification model, map those classes to existing lanes, make heavy families technically separable, add drift guards, document author guidance, and validate that the affected lanes still provide the intended confidence.
|
||||
- **Explicit non-goals**: No blanket reduction of Filament or Livewire coverage, no browser-strategy redesign, no wholesale rewrite of every UI test, no product runtime refactor, and no CI-matrix wiring rollout in this spec.
|
||||
- **Permanent complexity imported**: A repo-level heavy-test classification vocabulary, lane-mapping rules, heavy-family inventory, drift-guard expectations, and explicit heavy-lane budget visibility.
|
||||
- **Why now**: Spec 206 established lane governance and Spec 207 lowers per-test setup cost; without segmenting the heaviest UI and discovery families next, faster lanes will keep eroding as the platform surface grows.
|
||||
- **Why not local**: Local file moves or one-off exclusions cannot keep heavy families out of the wrong lane over time. The classification and placement rules must be shared, visible, and enforceable at repository level.
|
||||
- **Approval class**: Cleanup
|
||||
- **Red flags triggered**: New classification vocabulary and a new repo-wide taxonomy for heavy tests. Defense: the taxonomy is intentionally narrow, limited to test-lane governance, and does not introduce new product runtime truth, new product persistence, or speculative application abstractions.
|
||||
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexität: 1 | Produktnähe: 1 | Wiederverwendung: 2 | **Gesamt: 10/12**
|
||||
- **Decision**: approve
|
||||
|
||||
## Spec Scope Fields *(mandatory)*
|
||||
|
||||
- **Scope**: workspace
|
||||
- **Primary Routes**: No end-user HTTP routes change. The affected surfaces are repository-level lane manifests, grouping rules, wrapper entry points, profiling outputs, hotspot visibility, and checked-in author or reviewer guidance.
|
||||
- **Data Ownership**: Workspace-owned test taxonomy, family inventory, lane assignments, drift guards, heavy-lane budget rules, and reporting evidence. No tenant-owned runtime records or end-user product data are introduced.
|
||||
- **RBAC**: No end-user authorization behavior changes. The affected actors are repository contributors, reviewers, and maintainers who need stable, explicit lane placement for heavy UI and discovery tests.
|
||||
|
||||
## Proportionality Review *(mandatory when structural complexity is introduced)*
|
||||
|
||||
- **New source of truth?**: no
|
||||
- **New persisted entity/table/artifact?**: no new product persistence; only repository-owned classification metadata, checked-in guidance, and runtime evidence for lane validation
|
||||
- **New abstraction?**: yes, but limited to a repository-level heavy-suite classification and lane-mapping model
|
||||
- **New enum/state/reason family?**: no product runtime state is added; the new categories are test-governance classes only
|
||||
- **New cross-domain UI framework/taxonomy?**: yes, but narrowly scoped to classifying heavy test families by cost and purpose so lane placement remains reviewable and enforceable
|
||||
- **Current operator problem**: Contributors and maintainers cannot keep fast lanes lean when broad Filament or Livewire discovery and surface-governance families look similar to ordinary UI tests and drift into the wrong run path.
|
||||
- **Existing structure is insufficient because**: Directory names and current lane assignments do not reliably communicate discovery breadth, mount count, reflection cost, or surface-wide governance intent, so the real cost class is often only visible after runtime has already degraded.
|
||||
- **Narrowest correct implementation**: Add a small classification model, map it to existing lanes, inventory the heaviest current families, make those families technically separable, add drift guards, and publish concise author guidance.
|
||||
- **Ownership cost**: The team must maintain class definitions, lane-mapping rules, heavy-family inventory, drift guards, and heavy-budget visibility as the suite evolves.
|
||||
- **Alternative intentionally rejected**: Purely directory-based moves or ad-hoc exclusions without a shared semantic model, because those approaches hide cost instead of making it governable.
|
||||
- **Release truth**: Current-release repository truth that protects the lane model already introduced in Spec 206 and strengthened by Spec 207.
|
||||
|
||||
## Problem Statement
|
||||
|
||||
Even with slimmer fixtures, the suite stays expensive if the heaviest UI and discovery families remain organizationally mixed with ordinary feedback paths.
|
||||
|
||||
The structural issues are now clear:
|
||||
|
||||
- Filament and Livewire tests do not all belong to the same cost class.
|
||||
- Discovery-heavy and surface-wide guard tests grow with every new resource, page, relation manager, or system surface.
|
||||
- Broad UI or surface checks can compete directly with the fast authoring loop when they land in the wrong lane.
|
||||
- Lane placement is unstable if cost and purpose are not classified explicitly.
|
||||
- Some tests only reveal their real heaviness at runtime because they combine discovery, reflection, multiple mounts, and broad assertion sets in one file.
|
||||
- The most expensive families need their own budgets and visibility so runtime drift can be attributed accurately.
|
||||
|
||||
Without explicit segmentation, the Fast Feedback and Confidence lanes created by Spec 206 will gradually absorb more broad UI and discovery cost, even after per-test setup has been slimmed by Spec 207.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Depends on Spec 206 - Test Suite Governance & Performance Foundation for the lane vocabulary, wrappers, baseline visibility, and budget discipline.
|
||||
- Recommended after Spec 207 - Shared Test Fixture Slimming so per-test setup cost is reduced before the heaviest families are separated more sharply.
|
||||
- Blocks clean lane-based separation of expensive UI-, surface-, and discovery-heavy families.
|
||||
- Does not block ongoing feature delivery as long as newly added heavy tests are classified and placed lane-conform from the start.
|
||||
|
||||
## Goals
|
||||
|
||||
- Identify and classify heavy Filament, Livewire, surface, and discovery test families clearly.
|
||||
- Protect Fast Feedback from deliberate heavy UI and discovery cost.
|
||||
- Keep the Confidence lane broad enough to remain trusted while excluding surface-wide heavy governance families.
|
||||
- Establish Heavy Governance as the explicit home for surface-guard and discovery-heavy families.
|
||||
- Prevent new heavy tests from drifting silently into the wrong lane.
|
||||
- Preserve valuable Filament and Livewire safety while making its operating cost visible and governable.
|
||||
- Make future UI test growth scale through explicit class and lane rules rather than informal directory folklore.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Reducing legitimate Filament or Livewire coverage just to improve runtime headline numbers.
|
||||
- Redefining the Browser lane or folding browser behavior into this spec.
|
||||
- Replacing the fixture-cost work from Spec 207.
|
||||
- Rewriting every existing UI test into a new structure in one pass.
|
||||
- Performing CI-matrix or enforcement wiring that belongs in a later spec.
|
||||
- Changing Filament runtime semantics, application IA, or product behavior outside repository test organization and guidance.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- The lane model, wrappers, and baseline discipline from Spec 206 already exist or can be regenerated before this rollout is validated.
|
||||
- Fixture slimming from Spec 207 lowers per-test setup cost but does not by itself solve heavy-family placement.
|
||||
- Browser remains a separate cost class and is not merged into the heavy Filament or Livewire taxonomy.
|
||||
- Not every class needs its own dedicated directory, as long as its lane placement is explicit, technically separable, and reviewable.
|
||||
- Some borderline UI workflow tests may remain in Confidence if their scope is localized and their cost stays within documented lane budgets.
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Classify Existing Heavy UI Families (Priority: P1)
|
||||
|
||||
As a maintainer, I want the current heavy Filament, Livewire, surface, and discovery families inventoried and classified so the repository can stop treating them as a single undifferentiated UI cost bucket.
|
||||
|
||||
**Why this priority**: The rollout cannot be reliable until the existing heavy families are visible, named, and assigned a clear cost class.
|
||||
|
||||
**Independent Test**: Review the inventory produced by the rollout and confirm that each targeted heavy family has a named class, a purpose, a current lane, a target lane, and identifiable hotspot files.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a currently known heavy Filament or Livewire family, **When** the inventory is reviewed, **Then** its cost class, purpose, current lane, target lane, and hotspot files are documented.
|
||||
2. **Given** a family performs broad discovery, reflection, or surface-wide validation, **When** it is classified, **Then** it is not treated as ordinary UI-Light coverage.
|
||||
3. **Given** a family mixes UI interaction and governance-wide inspection, **When** it is reviewed, **Then** the broadest cost-driving behavior determines its class unless the family is split intentionally.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Keep Fast And Confidence Lanes Honest (Priority: P1)
|
||||
|
||||
As a contributor, I want Fast Feedback and Confidence to exclude the heaviest surface-wide and discovery-wide families so the normal authoring loop stays fast without hiding where broader governance checks now run.
|
||||
|
||||
**Why this priority**: Lane shaping only matters if the most commonly used runs stop paying for deliberately heavy families.
|
||||
|
||||
**Independent Test**: Run the affected lanes after segmentation and verify that Fast Feedback excludes Discovery-Heavy and broad Surface-Guard families, while Confidence retains only documented UI-Light and selected UI-Workflow coverage.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a family is classified as Discovery-Heavy or a broad Surface-Guard, **When** Fast Feedback runs, **Then** that family is excluded.
|
||||
2. **Given** a UI-Light or selected UI-Workflow family is explicitly approved for Confidence, **When** Confidence runs, **Then** it remains present without pulling in undocumented heavy governance families.
|
||||
3. **Given** a maintainer compares lane manifests after segmentation, **When** they inspect Fast Feedback and Confidence, **Then** the absence or inclusion of heavy UI families is explainable from written rules instead of local habit.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Catch Lane Drift During Review (Priority: P2)
|
||||
|
||||
As a reviewer, I want clear author rules and drift guards so a newly added heavy UI test cannot silently land in the wrong lane.
|
||||
|
||||
**Why this priority**: The segmentation work decays quickly if new tests can bypass it without being noticed.
|
||||
|
||||
**Independent Test**: Add or move a representative heavy test to the wrong lane and confirm that the repository's drift guard or validation path fails clearly enough for a reviewer to correct placement.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a new test performs broad resource discovery or surface-wide governance checks, **When** it is placed in Fast Feedback or ordinary Confidence by mistake, **Then** drift validation flags the mismatch.
|
||||
2. **Given** a reviewer inspects a new heavy UI test, **When** they read the repository guidance, **Then** they can determine whether the test is UI-Light, UI-Workflow, Surface-Guard, Discovery-Heavy, or Browser.
|
||||
3. **Given** a test belongs to the Browser class, **When** it is assigned to a non-browser lane, **Then** the repository rejects the placement.
|
||||
|
||||
---
|
||||
|
||||
### User Story 4 - Observe Heavy Budgets Separately (Priority: P2)
|
||||
|
||||
As a maintainer, I want the Heavy Governance lane and its heaviest families measured separately so runtime drift is visible without distorting the health signal of faster lanes.
|
||||
|
||||
**Why this priority**: Once heavy families are separated, the team still needs to see whether they are growing responsibly or becoming the next uncontrolled hotspot cluster.
|
||||
|
||||
**Independent Test**: Generate the heavy-lane reporting view after segmentation and confirm that the top hotspots are attributable to named heavy families or classes instead of appearing as undifferentiated suite slowdown.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** the Heavy Governance lane completes, **When** its reporting output is reviewed, **Then** the top hotspots are tied to named heavy families or classes.
|
||||
2. **Given** heavy-family runtime grows over time, **When** maintainers inspect the reporting signal, **Then** they can see whether the drift belongs to discovery-heavy, surface-guard, or workflow-heavy families.
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- A single file mixes a localized component assertion with broad discovery or reflection, making directory-only classification misleading.
|
||||
- A discovery-heavy family sits in a generic Feature location and looks cheap until it scans resources or relation managers at runtime.
|
||||
- A previously narrow UI test becomes heavy after adding multiple mounts, wide action-surface assertions, or broad reflection checks.
|
||||
- A borderline workflow test is too broad for Fast Feedback but still narrow enough for Confidence if its scope remains local and its cost stays within the documented budget.
|
||||
- Browser-like interaction depth must not be relabeled as heavy Filament or Livewire merely to avoid Browser-lane isolation.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
**Constitution alignment (required):** This feature changes no end-user routes, no Microsoft Graph behavior, no authorization plane, no queued operation semantics, and no operator-facing product surface. It does introduce a repository-level taxonomy for heavy UI and discovery tests, so the class model, lane mapping, drift guards, and validation evidence must remain explicit, reviewable, and measurable.
|
||||
|
||||
**Constitution alignment (PROP-001 / ABSTR-001 / BLOAT-001 / TEST-TRUTH-001):** The new structure is limited to repository test governance. It is justified because current lane drift is caused by real cost differences between heavy and ordinary UI families, and a narrower local move-only approach cannot keep those families out of the wrong lanes over time. The classification must stay small, cost-driven, and directly tied to business-useful confidence rather than creating an elaborate semantic framework.
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001 Heavy Classification Model**: The repository MUST define a documented classification model for heavy UI-related test families with at least these classes: UI-Light, UI-Workflow, Surface-Guard, Discovery-Heavy, and Browser.
|
||||
- **FR-002 Class Meaning**: Each class MUST have a written purpose, typical cost profile, characteristic behaviors, and boundary rules that explain why a test belongs there.
|
||||
- **FR-003 Cost-And-Purpose Placement**: Test classification MUST be determined by cost and purpose, including mount breadth, discovery breadth, reflection passes, assertion density, and surface coverage, rather than by directory name alone.
|
||||
- **FR-004 Lane Mapping Rules**: The repository MUST define binding lane-mapping rules stating which classes may appear in Fast Feedback, Confidence, Heavy Governance, and Browser.
|
||||
- **FR-005 Fast Feedback Protection**: Fast Feedback MUST exclude Discovery-Heavy families and broad Surface-Guard families. Only explicitly approved, low-cost UI-Light cases may remain there.
|
||||
- **FR-006 Confidence Preservation**: Confidence MUST retain documented UI-Light coverage and selected UI-Workflow coverage, while excluding undocumented discovery-wide or surface-wide heavy governance families. The repository MUST explain which UI safety remains in Confidence and why the exclusions are safe.
|
||||
- **FR-007 Heavy Governance Isolation**: Heavy Governance MUST be the explicit home for Surface-Guard, Discovery-Heavy, and other broad Filament or Livewire contract families whose cost or breadth makes them unsuitable for ordinary authoring loops.
|
||||
- **FR-008 Browser Isolation**: Browser MUST remain an independent class and lane. Browser tests MUST NOT diffuse into Fast Feedback, Confidence, or Heavy Governance.
|
||||
- **FR-009 Heavy Family Inventory**: The rollout MUST inventory the heaviest Filament, Livewire, surface, and discovery families touched by current lanes, documenting for each family its purpose, class, current lane, target lane, and hotspot files.
|
||||
- **FR-010 Technical Separability**: Heavy families MUST be technically separable through an explicit combination of grouping, manifest assignment, namespace or directory convention, wrapper metadata, or equivalent checked-in mechanism. Lane placement MUST NOT depend on unwritten team knowledge.
|
||||
- **FR-011 Surface Family Rationalization**: The heaviest action-surface, header-action, navigation-discipline, relation-manager, wizard, and discovery-wide governance families MUST be reviewed and intentionally placed in the correct class and lane.
|
||||
- **FR-012 Drift Guards**: The repository MUST include guard logic, validation tests, or an equivalent checked-in control that detects when Browser, Discovery-Heavy, or broad Surface-Guard families are assigned to the wrong lane.
|
||||
- **FR-013 Mixed-Family Handling**: When a file combines multiple behaviors with different cost classes, the repository MUST either split the file or classify it by its broadest cost-driving behavior and document that choice.
|
||||
- **FR-014 Heavy Budget Visibility**: Heavy Governance and other heavy UI family outputs MUST expose a lane budget, top hotspots, and named family or class attribution so runtime drift is visible over time.
|
||||
- **FR-015 Author And Reviewer Guidance**: Contributors and reviewers MUST have concise, binding guidance that explains when a test is UI-Light, UI-Workflow, Surface-Guard, Discovery-Heavy, or Browser, and when a test is too heavy for Fast Feedback or Confidence.
|
||||
- **FR-016 Validation Evidence**: After segmentation, the affected lanes MUST be validated successfully, and the validation evidence MUST show that faster lanes remain protected while heavy governance coverage is still runnable and observable.
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- **NFR-001 Fast-Lane Integrity**: Fast Feedback must remain stable or improved against its post-Spec 207 baseline and must measure lower wall-clock duration than Confidence and Heavy Governance in wrapper validation after segmentation.
|
||||
- **NFR-002 Review Clarity**: A reviewer must be able to determine the intended class and lane of a new heavy UI test from checked-in guidance and test-local signals without case-by-case reinvention.
|
||||
- **NFR-003 Observability**: Heavy-family runtime drift must be attributable to named classes or families rather than surfacing only as general suite slowdown.
|
||||
- **NFR-004 Scalability Readiness**: As the platform gains more resources, pages, and relation managers, growth in broad discovery or surface-governance coverage must scale primarily in Heavy Governance rather than silently inflating faster lanes.
|
||||
|
||||
## Work Packages
|
||||
|
||||
### Work Package A - Heavy Family Inventory
|
||||
|
||||
- Catalogue the current heavy Filament, Livewire, surface, and discovery families.
|
||||
- Record each family's purpose, cost class, current lane, target lane, and hotspot files.
|
||||
- Identify the most expensive broad-surface and discovery-heavy families that currently distort faster lanes.
|
||||
- Distinguish localized workflow coverage from governance-wide inspection families before any mechanical moves begin.
|
||||
|
||||
### Work Package B - Classification And Lane Rules
|
||||
|
||||
- Finalize the UI-Light, UI-Workflow, Surface-Guard, Discovery-Heavy, and Browser classes.
|
||||
- Define the lane-mapping rules for Fast Feedback, Confidence, Heavy Governance, and Browser.
|
||||
- Document boundary rules for borderline cases, especially mixed workflow and discovery behavior.
|
||||
- Publish concise reviewer rules so lane placement decisions stay consistent.
|
||||
|
||||
### Work Package C - Mechanical Segmentation
|
||||
|
||||
- Adjust groups, manifests, wrapper metadata, namespaces, directories, or other checked-in selectors so heavy families are technically separable.
|
||||
- Move or reassign the broadest heavy families into their correct lane.
|
||||
- Ensure ordinary faster lanes cannot inherit those families accidentally.
|
||||
- Keep the resulting structure visible enough that reviewers can understand placement intent immediately.
|
||||
|
||||
### Work Package D - Drift Guards
|
||||
|
||||
- Add validation that Browser families stay browser-only.
|
||||
- Add validation that Discovery-Heavy and broad Surface-Guard families do not drift into Fast Feedback or ordinary Confidence.
|
||||
- Protect against newly added heavy families being treated as ordinary UI tests without a classification decision.
|
||||
- Ensure mixed or borderline families are either split or explicitly justified.
|
||||
|
||||
### Work Package E - Lane Validation
|
||||
|
||||
- Validate Fast Feedback after heavy-family removal.
|
||||
- Validate Confidence after the intended UI-Light and UI-Workflow coverage remains.
|
||||
- Validate Heavy Governance as a runnable lane for deliberate surface and discovery cost.
|
||||
- Compare the resulting lane behavior against the post-Spec 207 baseline so the benefit and the retained confidence are both visible.
|
||||
|
||||
## Deliverables
|
||||
|
||||
- An inventory of the heavy Filament, Livewire, surface, and discovery families touched by this rollout.
|
||||
- A documented heavy-test classification model.
|
||||
- Updated lane mappings and checked-in technical separation for the targeted heavy families.
|
||||
- Drift guards that reject wrong-lane placement for Browser and heavy governance families.
|
||||
- Concise author and reviewer guidance for new heavy UI tests.
|
||||
- Validation evidence for Fast Feedback, Confidence, and Heavy Governance after segmentation.
|
||||
- Updated heavy-lane budget and hotspot visibility.
|
||||
|
||||
## Risks
|
||||
|
||||
### False Confidence Through Over-Segmentation
|
||||
|
||||
If too much meaningful UI safety leaves Confidence, the faster lane mix may look healthy while real product trust declines.
|
||||
|
||||
### Taxonomy Complexity
|
||||
|
||||
If the class model grows too many exceptions or subclasses, contributors may stop using it consistently.
|
||||
|
||||
### Mechanical Moves Without Semantic Clarity
|
||||
|
||||
If files are merely moved between groups or directories without a clear class model, the repository will hide cost rather than govern it.
|
||||
|
||||
### Reviewer Inconsistency
|
||||
|
||||
If reviewers do not apply the new class and lane rules consistently, heavy-family drift will return quickly.
|
||||
|
||||
### Hidden Cost Inside Ordinary Files
|
||||
|
||||
Some heavy behavior is only visible through runtime breadth, so directory rules alone can miss expensive mixed files unless drift guards are strong enough.
|
||||
|
||||
## Rollout Guidance
|
||||
|
||||
- Inventory the heavy families before changing structure.
|
||||
- Finalize the class model and lane rules before large mechanical moves.
|
||||
- Move the broadest and most obviously misplaced heavy families first.
|
||||
- Add drift guards before the rollout is considered complete.
|
||||
- Validate faster lanes and Heavy Governance separately after segmentation.
|
||||
- Document author and reviewer guidance only after the class model and lane rules are stable.
|
||||
- Avoid broad mechanical relocation before the semantic boundaries are explicit.
|
||||
|
||||
## Design Rules
|
||||
|
||||
- **Heavy must be explicit**: A broad UI, surface, or discovery family must never look like an ordinary test by accident.
|
||||
- **Fast feedback stays fast**: Fast Feedback must not inherit deliberate governance-wide Filament or Livewire cost.
|
||||
- **Confidence keeps real trust**: Confidence must still carry meaningful UI-Light and selected UI-Workflow safety.
|
||||
- **Discovery is governance**: Broad discovery and reflection families are governance checks, not convenience coverage.
|
||||
- **Browser is always separate**: Browser remains its own class and lane.
|
||||
- **Classification beats directory folklore**: The real test character decides placement, not the folder name alone.
|
||||
- **Reviewer intent must be immediate**: A reviewer must be able to see why a heavy test belongs where it does.
|
||||
|
||||
## Key Entities *(include if feature involves data)*
|
||||
|
||||
- **Heavy Test Class**: A repository-defined cost and purpose category used to describe a UI-related test family's expected behavior and lane fit.
|
||||
- **Heavy Test Family**: A named cluster of related tests, usually sharing a surface, workflow, discovery pattern, or governance concern.
|
||||
- **Lane Mapping Rule**: A checked-in rule that defines which test classes may or may not appear in each operational lane.
|
||||
- **Drift Guard**: A validation path that detects when a test family is assigned to a lane that conflicts with its class.
|
||||
- **Heavy Budget View**: The reporting signal that shows the runtime budget, top hotspots, and attribution for heavy families and their lane.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Every heavy Filament, Livewire, surface, or discovery family targeted by this rollout is classified, assigned a target lane, and documented with no unclassified exceptions remaining in the touched scope.
|
||||
- **SC-002**: Fast Feedback contains zero Discovery-Heavy families and zero broad Surface-Guard families after segmentation.
|
||||
- **SC-003**: Confidence contains only documented UI-Light families and explicitly selected UI-Workflow families, with zero undocumented heavy-governance exceptions.
|
||||
- **SC-004**: Heavy Governance reporting exposes at least the top 10 runtime hotspots with named family or class attribution.
|
||||
- **SC-005**: Wrong-lane placement for Browser, Discovery-Heavy, or broad Surface-Guard families is detected by a checked-in validation path before those tests become accepted lane members.
|
||||
- **SC-006**: Fast Feedback and Confidence are stable or improved against their post-Spec 207 baselines after segmentation, Fast Feedback remains lower wall-clock than Confidence and Heavy Governance in wrapper validation, and the documented UI safety coverage assigned to each lane remains intact.
|
||||
- **SC-007**: A reviewer can classify a newly added heavy UI-related test using checked-in guidance alone, without relying on undocumented local knowledge.
|
||||
181
specs/208-heavy-suite-segmentation/tasks.md
Normal file
181
specs/208-heavy-suite-segmentation/tasks.md
Normal file
@ -0,0 +1,181 @@
|
||||
# Tasks: Filament/Livewire Heavy Suite Segmentation
|
||||
|
||||
**Input**: Design documents from `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/208-heavy-suite-segmentation/`
|
||||
**Prerequisites**: `plan.md` (required), `spec.md` (required), `research.md`, `data-model.md`, `contracts/`, `quickstart.md`
|
||||
|
||||
**Tests**: Required. This feature changes repository test-governance behavior, so each user story includes Pest guard or contract coverage and focused validation runs through Sail.
|
||||
|
||||
**Organization**: Tasks are grouped by user story so each story can be implemented and validated independently.
|
||||
|
||||
## Phase 1: Setup (Shared Context)
|
||||
|
||||
**Purpose**: Confirm the existing lane seams, guard anchors, and hotspot files that Spec 208 will segment.
|
||||
|
||||
- [X] T001 Audit lane command and artifact entry points in `apps/platform/composer.json`, `scripts/platform-test-lane`, and `scripts/platform-test-report`
|
||||
- [X] T002 [P] Review the current manifest and shared guard anchors in `apps/platform/tests/Support/TestLaneManifest.php`, `apps/platform/tests/Feature/Guards/TestLaneManifestTest.php`, and `apps/platform/tests/Feature/Guards/TestLaneArtifactsContractTest.php`
|
||||
- [X] T003 [P] Review the seeded hotspot clusters in `apps/platform/tests/Feature/Guards/ActionSurfaceContractTest.php`, `apps/platform/tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php`, `apps/platform/tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php`, `apps/platform/tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php`, `apps/platform/tests/Feature/Rbac/OnboardingWizardUiEnforcementTest.php`, `apps/platform/tests/Feature/Filament/TenantReviewHeaderDisciplineTest.php`, `apps/platform/tests/Feature/Filament/PanelNavigationSegregationTest.php`, and `apps/platform/tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Blocking Prerequisites)
|
||||
|
||||
**Purpose**: Add the shared manifest and artifact seams that all user stories rely on.
|
||||
|
||||
**Critical**: No user story work should begin until these tasks are complete.
|
||||
|
||||
- [X] T004 Extend `apps/platform/tests/Support/TestLaneManifest.php` with neutral accessors for classification catalogs, family inventory, lane placement rules, and mixed-file resolution to back the logical read operations in `specs/208-heavy-suite-segmentation/contracts/heavy-suite-segmentation.logical.openapi.yaml`
|
||||
- [X] T005 [P] Update `apps/platform/tests/Feature/Guards/TestLaneManifestTest.php` to accept the expanded manifest metadata shape without locking story-specific families yet
|
||||
- [X] T006 [P] Update `apps/platform/tests/Feature/Guards/TestLaneArtifactsContractTest.php` to reserve class and family attribution keys under `apps/platform/storage/logs/test-lanes/`
|
||||
|
||||
**Checkpoint**: Shared manifest and artifact seams are ready for story-specific implementation.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 - Classify Existing Heavy UI Families (Priority: P1)
|
||||
|
||||
**Goal**: Seed a checked-in heavy-family catalog that names the first heavy Filament, Livewire, surface, discovery, and browser families with explicit purpose and lane intent.
|
||||
|
||||
**Independent Test**: Review the manifest inventory and guard coverage to confirm each seeded family has a class, purpose, current lane, target lane, selectors, hotspot files, and validation status.
|
||||
|
||||
### Tests for User Story 1
|
||||
|
||||
- [X] T007 [P] [US1] Expand `apps/platform/tests/Feature/Guards/TestTaxonomyPlacementGuardTest.php` to assert the five-class catalog, allowed lanes, and forbidden lane semantics from `specs/208-heavy-suite-segmentation/contracts/heavy-test-classification.schema.json`
|
||||
- [X] T008 [P] [US1] Expand `apps/platform/tests/Feature/Guards/TestLaneManifestTest.php` to assert every seeded heavy family in `apps/platform/tests/Support/TestLaneManifest.php` exposes purpose, current lane, target lane, selectors, hotspot files, validation status, and confidence rationale whenever the target lane is `confidence`
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [X] T009 [US1] Populate the `ui-light`, `ui-workflow`, `surface-guard`, `discovery-heavy`, and `browser` catalog plus lane placement rules in `apps/platform/tests/Support/TestLaneManifest.php` and expose the classification catalog through the logical read surface defined in `specs/208-heavy-suite-segmentation/contracts/heavy-suite-segmentation.logical.openapi.yaml`
|
||||
- [X] T010 [US1] Seed the first heavy family inventory in `apps/platform/tests/Support/TestLaneManifest.php` using `apps/platform/tests/Feature/Guards/ActionSurfaceContractTest.php`, `apps/platform/tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php`, `apps/platform/tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php`, `apps/platform/tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php`, `apps/platform/tests/Feature/Rbac/OnboardingWizardUiEnforcementTest.php`, `apps/platform/tests/Feature/Filament/TenantReviewHeaderDisciplineTest.php`, `apps/platform/tests/Feature/Filament/PanelNavigationSegregationTest.php`, and `apps/platform/tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php`, and expose the family inventory through the logical read surface defined in `specs/208-heavy-suite-segmentation/contracts/heavy-suite-segmentation.logical.openapi.yaml`
|
||||
- [X] T011 [US1] Record mixed-file resolution and hotspot cost signals in `apps/platform/tests/Support/TestLaneManifest.php` for `apps/platform/tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php`, `apps/platform/tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php`, and `apps/platform/tests/Feature/Concerns/BuildsBaselineCompareMatrixFixtures.php`, and keep those records consistent with `specs/208-heavy-suite-segmentation/contracts/heavy-test-classification.schema.json` and `specs/208-heavy-suite-segmentation/contracts/heavy-suite-segmentation.logical.openapi.yaml`
|
||||
- [X] T012 [US1] Align the seeded family inventory examples in `specs/208-heavy-suite-segmentation/quickstart.md` with the canonical catalog stored in `apps/platform/tests/Support/TestLaneManifest.php` without publishing final reviewer guidance yet
|
||||
|
||||
**Checkpoint**: User Story 1 is complete when the seeded heavy-family inventory is reviewable and contract-tested without changing lane membership yet.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 - Keep Fast And Confidence Lanes Honest (Priority: P1)
|
||||
|
||||
**Goal**: Move discovery-heavy and broad surface-guard families out of Fast Feedback and ordinary Confidence while preserving documented `ui-light` and selected `ui-workflow` trust.
|
||||
|
||||
**Independent Test**: Run the lane contract guards and wrapper flows to verify Fast Feedback excludes discovery-heavy and broad surface-guard families, Confidence retains only the documented light or workflow families, and Heavy Governance receives the moved families.
|
||||
|
||||
### Tests for User Story 2
|
||||
|
||||
- [X] T013 [P] [US2] Expand `apps/platform/tests/Feature/Guards/FastFeedbackLaneContractTest.php` and `apps/platform/tests/Feature/Guards/FastFeedbackLaneExclusionTest.php` to assert discovery-heavy and broad surface-guard families never run in Fast Feedback
|
||||
- [X] T014 [P] [US2] Expand `apps/platform/tests/Feature/Guards/ConfidenceLaneContractTest.php` and `apps/platform/tests/Feature/Guards/HeavyGovernanceLaneContractTest.php` to assert only documented `ui-light` and selected `ui-workflow` families with explicit confidence rationale remain in Confidence while moved families land in Heavy Governance
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [X] T015 [US2] Rework Fast Feedback, Confidence, and Heavy Governance membership rules in `apps/platform/tests/Support/TestLaneManifest.php` to consume the seeded family inventory instead of scattered file exclusions
|
||||
- [X] T016 [US2] Add granular `ui-light`, `ui-workflow`, `surface-guard`, `discovery-heavy`, and `browser` grouping rules in `apps/platform/tests/Pest.php` for the seeded hotspot files
|
||||
- [X] T017 [P] [US2] Reclassify discovery-heavy Filament parity hotspots in `apps/platform/tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php` and `apps/platform/tests/Feature/Filament/PolicyVersionAdminSearchParityTest.php`
|
||||
- [X] T018 [P] [US2] Reclassify workflow and matrix hotspots in `apps/platform/tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php` and `apps/platform/tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php`
|
||||
- [X] T019 [P] [US2] Reclassify fixture-amplifying concern hotspots in `apps/platform/tests/Feature/Concerns/BuildsBaselineCompareMatrixFixtures.php` and `apps/platform/tests/Feature/Concerns/BuildsPortfolioTriageFixtures.php`
|
||||
- [X] T020 [P] [US2] Reclassify broad relation-manager, RBAC, and wizard hotspots in `apps/platform/tests/Feature/Rbac/BackupItemsRelationManagerUiEnforcementTest.php`, `apps/platform/tests/Feature/Rbac/WorkspaceMembershipsRelationManagerUiEnforcementTest.php`, and `apps/platform/tests/Feature/Rbac/OnboardingWizardUiEnforcementTest.php`
|
||||
- [X] T021 [US2] Reclassify header-action and navigation-discipline hotspots in `apps/platform/tests/Feature/Filament/TenantReviewHeaderDisciplineTest.php` and `apps/platform/tests/Feature/Filament/PanelNavigationSegregationTest.php`, then reconcile lane entry point metadata in `apps/platform/composer.json` and `scripts/platform-test-lane` with the segmented family selectors from `apps/platform/tests/Support/TestLaneManifest.php`
|
||||
|
||||
**Checkpoint**: User Story 2 is complete when lane membership is explainable from cataloged family rules and faster lanes no longer carry the prohibited heavy families.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 3 - Catch Lane Drift During Review (Priority: P2)
|
||||
|
||||
**Goal**: Make wrong-lane placement fail clearly for Browser, Discovery-Heavy, and broad Surface-Guard families, with contributor guidance that keeps classification decisions reviewable.
|
||||
|
||||
**Independent Test**: Introduce a representative wrong-lane placement locally and confirm the guard suite fails with actionable output naming the violating class, family, or file.
|
||||
|
||||
### Tests for User Story 3
|
||||
|
||||
- [X] T022 [P] [US3] Expand `apps/platform/tests/Feature/Guards/BrowserLaneIsolationTest.php` and `apps/platform/tests/Feature/Guards/TestTaxonomyPlacementGuardTest.php` to fail on browser misplacement and invalid class-to-lane mappings
|
||||
- [X] T023 [P] [US3] Expand `apps/platform/tests/Feature/Guards/ConfidenceLaneContractTest.php` and `apps/platform/tests/Feature/Guards/HeavyGovernanceLaneContractTest.php` to cover mixed-file fallback and wrong-lane regression cases
|
||||
|
||||
### Implementation for User Story 3
|
||||
|
||||
- [X] T024 [US3] Implement manifest-side lane placement validation and mixed-file resolution helpers in `apps/platform/tests/Support/TestLaneManifest.php` consistent with `specs/208-heavy-suite-segmentation/contracts/heavy-suite-segmentation.logical.openapi.yaml`
|
||||
- [X] T025 [US3] Add failure-friendly family and class lookup output for guard consumers in `apps/platform/tests/Support/TestLaneManifest.php` and `apps/platform/tests/Feature/Guards/TestLaneManifestTest.php`
|
||||
- [X] T026 [US3] Publish contributor and reviewer classification guidance in `specs/208-heavy-suite-segmentation/quickstart.md` and keep the canonical reviewer signals in `apps/platform/tests/Support/TestLaneManifest.php`
|
||||
|
||||
**Checkpoint**: User Story 3 is complete when wrong-lane drift fails automatically and reviewers can classify new heavy tests from checked-in guidance alone.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: User Story 4 - Observe Heavy Budgets Separately (Priority: P2)
|
||||
|
||||
**Goal**: Attribute Heavy Governance cost by lane, class, and family so hotspot growth is visible without distorting the signal of faster lanes.
|
||||
|
||||
**Independent Test**: Generate heavy-governance report artifacts and confirm they expose the top hotspots, class attribution, family attribution, and budget evaluations under the existing artifact root.
|
||||
|
||||
### Tests for User Story 4
|
||||
|
||||
- [X] T027 [P] [US4] Expand `apps/platform/tests/Feature/Guards/TestLaneArtifactsContractTest.php` and `apps/platform/tests/Feature/Guards/ProfileLaneContractTest.php` to assert heavy reports emit the canonical top 10 runtime hotspot view ordered by slowest entries plus class attribution, family attribution, and budget evaluation payloads
|
||||
- [X] T028 [P] [US4] Expand `apps/platform/tests/Feature/Guards/FixtureLaneImpactBudgetTest.php` and `apps/platform/tests/Feature/Guards/HeavyGovernanceLaneContractTest.php` to assert lane-, class-, and family-level budget targets for Heavy Governance
|
||||
|
||||
### Implementation for User Story 4
|
||||
|
||||
- [X] T029 [US4] Extend `apps/platform/tests/Support/TestLaneBudget.php` with lane, class, and family budget targets plus baseline sources, enforcement levels, and lifecycle states for the seeded heavy families
|
||||
- [X] T030 [US4] Extend `apps/platform/tests/Support/TestLaneReport.php` to emit the canonical top 10 runtime hotspot view ordered by slowest entries, classification attribution, family attribution, budget evaluations, and artifact paths under `apps/platform/storage/logs/test-lanes/` consistent with `specs/208-heavy-suite-segmentation/contracts/heavy-suite-segmentation.logical.openapi.yaml`
|
||||
- [X] T031 [US4] Update `scripts/platform-test-report` and `apps/platform/composer.json` to surface the heavy attribution report contract emitted by `apps/platform/tests/Support/TestLaneReport.php` and preserve reviewable top 10 runtime hotspot output for Heavy Governance
|
||||
|
||||
**Checkpoint**: User Story 4 is complete when Heavy Governance artifacts explain slowdowns by class and family, not only by lane total.
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: Validate the end-to-end rollout, format the code, and remove superseded ad-hoc lane logic.
|
||||
|
||||
- [X] T032 Run focused Pest coverage for `apps/platform/tests/Feature/Guards/`, `apps/platform/tests/Feature/Filament/`, `apps/platform/tests/Feature/Baselines/`, and `apps/platform/tests/Feature/Rbac/` with `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards tests/Feature/Filament tests/Feature/Baselines tests/Feature/Rbac`
|
||||
- [X] T033 Run lane wrapper validation with `scripts/platform-test-lane` and `scripts/platform-test-report`, compare Fast Feedback and Confidence against the post-Spec 207 baselines, confirm Fast Feedback measures lower wall-clock than Confidence and Heavy Governance, record Heavy Governance threshold or refreshed baseline evidence, and inspect emitted artifacts under `apps/platform/storage/logs/test-lanes/`
|
||||
- [X] T034 Run formatting for `apps/platform/` with `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
|
||||
- [X] T035 Remove stale ad-hoc lane comments or exclusions in `apps/platform/tests/Pest.php`, `apps/platform/tests/Support/TestLaneManifest.php`, and `apps/platform/tests/Support/TestLaneBudget.php` once the segmented catalog is authoritative
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Phase 1 must complete before Phase 2.
|
||||
- Phase 2 must complete before any user story work begins.
|
||||
- User Story 1 is the MVP and must complete before User Story 2, User Story 3, or User Story 4.
|
||||
- User Story 2 depends on User Story 1 because lane membership must consume the seeded family inventory.
|
||||
- User Story 3 depends on User Story 1 and User Story 2 because drift validation relies on the final catalog and lane mappings.
|
||||
- User Story 4 depends on User Story 1 and User Story 2 because report attribution relies on the seeded family inventory and segmented lane ownership.
|
||||
- Phase 7 depends on all user stories.
|
||||
|
||||
## Parallel Execution Examples
|
||||
|
||||
### User Story 1
|
||||
|
||||
- Run T007 and T008 in parallel to lock the schema and inventory guard coverage.
|
||||
- After T009 lands, T010 and T011 can be split between seeded family inventory work and mixed-file resolution work.
|
||||
|
||||
### User Story 2
|
||||
|
||||
- Run T013 and T014 in parallel because they cover different lane guard files.
|
||||
- After T015 and T016 land, T017, T018, T019, and T020 can run in parallel across Filament, Baselines, Concerns, and RBAC hotspots.
|
||||
|
||||
### User Story 3
|
||||
|
||||
- Run T022 and T023 in parallel because they touch separate guard surfaces.
|
||||
- T024 should land before T025 and T026 so the guidance and failure output describe the actual validation helpers.
|
||||
|
||||
### User Story 4
|
||||
|
||||
- Run T027 and T028 in parallel because artifact-contract and budget-guard coverage live in different test files.
|
||||
- After T029 lands, T030 and T031 can be split between report payload generation and wrapper exposure work.
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP First
|
||||
|
||||
- Deliver User Story 1 first to establish the canonical heavy-family catalog, seeded inventory, and reviewable hotspot metadata.
|
||||
- Deliver User Story 2 next to produce the first operational benefit by keeping Fast Feedback and Confidence honest.
|
||||
|
||||
### Incremental Delivery
|
||||
|
||||
- Add User Story 3 after lane segmentation so drift protection describes the real final placement rules instead of a temporary draft.
|
||||
- Add User Story 4 after the lane moves settle so the attribution model reflects stable family ownership and budget targets.
|
||||
|
||||
### Validation Sequence
|
||||
|
||||
- Use focused guard suites first, then targeted hotspot files, then the lane wrappers, and only then refresh formatting and stale exclusion cleanup.
|
||||
- Treat `apps/platform/storage/logs/test-lanes/` as the canonical artifact root throughout validation and report review.
|
||||
Loading…
Reference in New Issue
Block a user