chore: commit all changes (automated)
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m39s

This commit is contained in:
Ahmed Darrazi 2026-05-05 12:38:55 +02:00
parent a146b14208
commit 4256e642ed
18 changed files with 1344 additions and 9 deletions

View File

@ -161,6 +161,15 @@ public function handle(
'previous_current_snapshot_exists' => $previousCurrentSnapshotExists,
],
);
$context['progress'] = array_merge(
is_array($context['progress'] ?? null) ? $context['progress'] : [],
[
'phase' => [
'key' => 'preparing',
'label' => 'Preparing baseline capture.',
],
],
);
$this->operationRun->update(['context' => $context]);
$this->operationRun->refresh();
$context = is_array($this->operationRun->context) ? $this->operationRun->context : [];
@ -284,6 +293,19 @@ public function handle(
$resumeToken = null;
if ($captureMode === BaselineCaptureMode::FullContent) {
$processingContext = is_array($this->operationRun->context) ? $this->operationRun->context : [];
$processingContext['progress'] = array_merge(
is_array($processingContext['progress'] ?? null) ? $processingContext['progress'] : [],
[
'phase' => [
'key' => 'processing',
'label' => 'Capturing evidence.',
],
],
);
$this->operationRun->update(['context' => $processingContext]);
$this->operationRun->refresh();
$budgets = [
'max_items_per_run' => (int) config('tenantpilot.baselines.full_content_capture.max_items_per_run', 200),
'max_concurrency' => (int) config('tenantpilot.baselines.full_content_capture.max_concurrency', 5),
@ -348,6 +370,19 @@ public function handle(
],
];
$persistingContext = is_array($this->operationRun->context) ? $this->operationRun->context : [];
$persistingContext['progress'] = array_merge(
is_array($persistingContext['progress'] ?? null) ? $persistingContext['progress'] : [],
[
'phase' => [
'key' => 'persisting',
'label' => 'Saving baseline snapshot.',
],
],
);
$this->operationRun->update(['context' => $persistingContext]);
$this->operationRun->refresh();
if ($subjectsTotal === 0) {
$snapshotResult = $this->captureNoDataSnapshotArtifact(
$profile,
@ -395,6 +430,15 @@ public function handle(
'current_baseline_changed' => false,
],
);
$updatedContext['progress'] = array_merge(
is_array($updatedContext['progress'] ?? null) ? $updatedContext['progress'] : [],
[
'phase' => [
'key' => 'finalizing',
'label' => 'Finalizing baseline capture.',
],
],
);
$this->operationRun->update([
'context' => $updatedContext,
@ -496,6 +540,15 @@ public function handle(
'snapshot_lifecycle' => $snapshot->lifecycleState()->value,
'current_baseline_changed' => $currentBaselineChanged,
];
$updatedContext['progress'] = array_merge(
is_array($updatedContext['progress'] ?? null) ? $updatedContext['progress'] : [],
[
'phase' => [
'key' => 'finalizing',
'label' => 'Finalizing baseline capture.',
],
],
);
$this->operationRun->update(['context' => $updatedContext]);
$this->auditCompleted(

View File

@ -338,6 +338,15 @@ public function handle(
$strategySelection = $compareStrategyRegistry->select($effectiveScope);
$context = $this->withCompareStrategySelection($context, $strategySelection);
$context['progress'] = array_merge(
is_array($context['progress'] ?? null) ? $context['progress'] : [],
[
'phase' => [
'key' => 'preparing',
'label' => 'Preparing baseline comparison.',
],
],
);
$this->operationRun->update(['context' => $context]);
$this->operationRun->refresh();
@ -429,6 +438,19 @@ public function handle(
$resumeToken = null;
if ($captureMode === BaselineCaptureMode::FullContent) {
$processingContext = is_array($this->operationRun->context) ? $this->operationRun->context : [];
$processingContext['progress'] = array_merge(
is_array($processingContext['progress'] ?? null) ? $processingContext['progress'] : [],
[
'phase' => [
'key' => 'processing',
'label' => 'Refreshing comparison evidence.',
],
],
);
$this->operationRun->update(['context' => $processingContext]);
$this->operationRun->refresh();
$budgets = [
'max_items_per_run' => (int) config('tenantpilot.baselines.full_content_capture.max_items_per_run', 200),
'max_concurrency' => (int) config('tenantpilot.baselines.full_content_capture.max_concurrency', 5),
@ -512,6 +534,19 @@ public function handle(
launchContext: is_array($context['baseline_compare'] ?? null) ? $context['baseline_compare'] : [],
);
$comparePhaseContext = is_array($this->operationRun->context) ? $this->operationRun->context : [];
$comparePhaseContext['progress'] = array_merge(
is_array($comparePhaseContext['progress'] ?? null) ? $comparePhaseContext['progress'] : [],
[
'phase' => [
'key' => 'processing',
'label' => 'Evaluating baseline drift.',
],
],
);
$this->operationRun->update(['context' => $comparePhaseContext]);
$this->operationRun->refresh();
try {
$compareResult = $strategy->compare(
context: $orchestrationContext,
@ -694,6 +729,15 @@ public function handle(
'counts_by_change_type' => $countsByChangeType,
],
);
$updatedContext['progress'] = array_merge(
is_array($updatedContext['progress'] ?? null) ? $updatedContext['progress'] : [],
[
'phase' => [
'key' => 'finalizing',
'label' => 'Finalizing baseline comparison.',
],
],
);
$updatedContext['result'] = [
'findings_total' => count($driftResults),
'findings_upserted' => (int) $upsertResult['processed_count'],
@ -944,6 +988,15 @@ private function completeWithCoverageWarning(
'counts_by_change_type' => [],
],
);
$updatedContext['progress'] = array_merge(
is_array($updatedContext['progress'] ?? null) ? $updatedContext['progress'] : [],
[
'phase' => [
'key' => 'finalizing',
'label' => 'Finalizing baseline comparison.',
],
],
);
$updatedContext['result'] = [
'findings_total' => 0,
'findings_upserted' => 0,

View File

@ -84,6 +84,12 @@ public function startCapture(
'entra_tenant_id' => $sourceTenant->graphTenantId(),
'entra_tenant_name' => (string) $sourceTenant->name,
],
'progress' => [
'phase' => [
'key' => 'preparing',
'label' => 'Preparing baseline capture.',
],
],
'baseline_profile_id' => (int) $profile->getKey(),
'source_tenant_id' => (int) $sourceTenant->getKey(),
'effective_scope' => $effectiveScope->toEffectiveScopeContext($this->capabilityGuard, 'capture'),

View File

@ -144,6 +144,12 @@ public function startCompareForProfile(
'entra_tenant_id' => $tenant->graphTenantId(),
'entra_tenant_name' => (string) $tenant->name,
],
'progress' => [
'phase' => [
'key' => 'preparing',
'label' => 'Preparing baseline comparison.',
],
],
'baseline_profile_id' => (int) $profile->getKey(),
'baseline_snapshot_id' => $snapshotId,
'effective_scope' => $effectiveScope->toEffectiveScopeContext($this->capabilityGuard, 'compare'),

View File

@ -170,6 +170,9 @@ private function queueComposition(
'evidence_snapshot_id' => (int) $snapshot->getKey(),
'review_fingerprint' => $fingerprint,
'review_id' => $existingReview?->getKey(),
'progress' => [
'composite' => $this->reviewComposeProgressMetadata($snapshot),
],
],
initiator: $user,
);
@ -247,4 +250,71 @@ private function findExistingMutableReview(Tenant $tenant, string $fingerprint):
->latest('id')
->first();
}
/**
* @return array{label: string, operation_count: int, failed_count: int, partial_count: int}
*/
private function reviewComposeProgressMetadata(EvidenceSnapshot $snapshot): array
{
$snapshot->loadMissing('items');
$operationsSummary = $snapshot->items
->firstWhere('dimension_key', 'operations_summary')
?->summary_payload;
$operationCount = is_numeric(data_get($operationsSummary, 'operation_count'))
? (int) data_get($operationsSummary, 'operation_count')
: 0;
$failedCount = is_numeric(data_get($operationsSummary, 'failed_count'))
? (int) data_get($operationsSummary, 'failed_count')
: 0;
$partialCount = is_numeric(data_get($operationsSummary, 'partial_count'))
? (int) data_get($operationsSummary, 'partial_count')
: 0;
return [
'label' => $this->reviewComposeProgressLabel($operationCount, $failedCount, $partialCount),
'operation_count' => $operationCount,
'failed_count' => $failedCount,
'partial_count' => $partialCount,
];
}
private function reviewComposeProgressLabel(int $operationCount, int $failedCount, int $partialCount): string
{
$baseLabel = $operationCount > 0
? sprintf('Review composition is aggregating %d %s.', $operationCount, $operationCount === 1 ? 'operation' : 'operations')
: 'Review composition is aggregating related operations.';
if ($failedCount > 0 && $partialCount > 0) {
return sprintf(
'%s %d %s and %d %s currently need review.',
$baseLabel,
$failedCount,
$failedCount === 1 ? 'failed operation' : 'failed operations',
$partialCount,
$partialCount === 1 ? 'partial operation' : 'partial operations',
);
}
if ($failedCount > 0) {
return sprintf(
'%s %d %s currently need review.',
$baseLabel,
$failedCount,
$failedCount === 1 ? 'failed operation' : 'failed operations',
);
}
if ($partialCount > 0) {
return sprintf(
'%s %d %s currently need review.',
$baseLabel,
$partialCount,
$partialCount === 1 ? 'partial operation' : 'partial operations',
);
}
return $baseLabel;
}
}

View File

@ -40,8 +40,8 @@ public static function forRun(OperationRun $run): array
return match ($capability) {
self::COUNTED => self::countedModel($summaryCounts),
self::PHASED => self::indeterminateModel(self::PHASED, 'Phase progress pending.'),
self::COMPOSITE => self::indeterminateModel(self::COMPOSITE, 'Composite progress pending.'),
self::PHASED => self::phasedModel($run, $context),
self::COMPOSITE => self::compositeModel($run, $summaryCounts, $context),
self::ACTIVITY => self::indeterminateModel(
self::ACTIVITY,
(string) $run->status === OperationRunStatus::Queued->value
@ -121,6 +121,52 @@ private static function countedModel(array $summaryCounts): array
];
}
/**
* @param array<string, mixed> $context
* @return array{
* capability: string,
* display: string,
* label: string,
* processed: null,
* total: null,
* percent: null
* }
*/
private static function phasedModel(OperationRun $run, array $context): array
{
$phase = self::phaseProgressMetadata($context);
if ($phase !== null) {
return self::indeterminateModel(self::PHASED, $phase['label']);
}
return self::indeterminateModel(
self::PHASED,
self::legacyPhasedLabel($run, $context) ?? 'Phase progress pending.',
);
}
/**
* @param array<string, int> $summaryCounts
* @param array<string, mixed> $context
* @return array{
* capability: string,
* display: string,
* label: string,
* processed: null,
* total: null,
* percent: null
* }
*/
private static function compositeModel(OperationRun $run, array $summaryCounts, array $context): array
{
$label = self::explicitCompositeLabel($context)
?? self::legacyCompositeLabel($run, $summaryCounts, $context)
?? 'Composite progress pending.';
return self::indeterminateModel(self::COMPOSITE, $label);
}
/**
* @return array{
* capability: string,
@ -170,6 +216,10 @@ private static function noneModel(): array
*/
private static function hasPhasedHint(array $context): bool
{
if (self::phaseProgressMetadata($context) !== null) {
return true;
}
foreach (['baseline_capture.evidence_capture', 'baseline_compare.evidence_capture'] as $path) {
$phaseStats = data_get($context, $path);
@ -198,6 +248,10 @@ private static function looksLikePhaseStats(array $phaseStats): bool
*/
private static function hasCompositeHint(array $summaryCounts, array $context): bool
{
if (self::explicitCompositeLabel($context) !== null) {
return true;
}
$operationCount = $summaryCounts['operation_count'] ?? null;
if (is_int($operationCount) && $operationCount > 1) {
@ -220,4 +274,141 @@ private static function hasCompositeHint(array $summaryCounts, array $context):
return false;
}
/**
* @param array<string, mixed> $context
* @return array{key: string, label: string}|null
*/
private static function phaseProgressMetadata(array $context): ?array
{
$phase = data_get($context, 'progress.phase');
if (! is_array($phase)) {
return null;
}
$key = self::cleanString($phase['key'] ?? null);
if ($key === null || ! in_array($key, ['preparing', 'fetching', 'processing', 'persisting', 'finalizing'], true)) {
return null;
}
$label = self::cleanString($phase['label'] ?? null) ?? self::defaultPhaseLabel($key);
return [
'key' => $key,
'label' => $label,
];
}
private static function defaultPhaseLabel(string $key): string
{
return match ($key) {
'preparing' => 'Preparing work.',
'fetching' => 'Collecting required evidence.',
'processing' => 'Processing current work.',
'persisting' => 'Saving results.',
'finalizing' => 'Finalizing operation.',
default => 'Phase progress pending.',
};
}
/**
* @param array<string, mixed> $context
*/
private static function explicitCompositeLabel(array $context): ?string
{
return self::cleanString(data_get($context, 'progress.composite.label'));
}
/**
* @param array<string, mixed> $context
*/
private static function legacyPhasedLabel(OperationRun $run, array $context): ?string
{
return match ((string) $run->type) {
'baseline_capture' => is_array(data_get($context, 'baseline_capture.evidence_capture'))
? 'Capturing evidence.'
: null,
'baseline_compare' => is_array(data_get($context, 'baseline_compare.evidence_capture'))
? 'Refreshing comparison evidence.'
: null,
default => null,
};
}
/**
* @param array<string, int> $summaryCounts
* @param array<string, mixed> $context
*/
private static function legacyCompositeLabel(OperationRun $run, array $summaryCounts, array $context): ?string
{
if ((string) $run->type !== 'tenant.review.compose') {
return null;
}
$operationCount = self::intOrNull($summaryCounts['operation_count'] ?? data_get($context, 'progress.composite.operation_count'));
$failedCount = self::intOrNull(data_get($context, 'progress.composite.failed_count'));
$partialCount = self::intOrNull(data_get($context, 'progress.composite.partial_count'));
$baseLabel = $operationCount !== null && $operationCount > 0
? sprintf('Review composition is aggregating %d %s.', $operationCount, $operationCount === 1 ? 'operation' : 'operations')
: 'Review composition is aggregating related operations.';
$attentionLabel = self::compositeAttentionLabel($failedCount, $partialCount);
return $attentionLabel === null
? $baseLabel
: sprintf('%s %s', $baseLabel, $attentionLabel);
}
private static function compositeAttentionLabel(?int $failedCount, ?int $partialCount): ?string
{
$failedCount = $failedCount !== null && $failedCount > 0 ? $failedCount : null;
$partialCount = $partialCount !== null && $partialCount > 0 ? $partialCount : null;
if ($failedCount === null && $partialCount === null) {
return null;
}
if ($failedCount !== null && $partialCount !== null) {
return sprintf(
'%d %s and %d %s currently need review.',
$failedCount,
$failedCount === 1 ? 'failed operation' : 'failed operations',
$partialCount,
$partialCount === 1 ? 'partial operation' : 'partial operations',
);
}
if ($failedCount !== null) {
return sprintf(
'%d %s currently need review.',
$failedCount,
$failedCount === 1 ? 'failed operation' : 'failed operations',
);
}
return sprintf(
'%d %s currently need review.',
$partialCount,
$partialCount === 1 ? 'partial operation' : 'partial operations',
);
}
private static function cleanString(mixed $value): ?string
{
if (! is_string($value)) {
return null;
}
$value = trim($value);
return $value === '' ? null : $value;
}
private static function intOrNull(mixed $value): ?int
{
return is_int($value) ? $value : null;
}
}

View File

@ -259,7 +259,7 @@ function operationActivityFeedbackSmokeLoginUrl(User $user, Tenant $tenant, stri
visit(InventoryItemResource::getUrl('index', panel: 'tenant', tenant: $tenant))
->resize(1440, 1200)
->waitForText('Inventory Items')
->waitForText('Phase progress pending.')
->waitForText('Capturing evidence.')
->assertSee('View operation')
->assertDontSee('4 / 10 processed (40%)')
->assertScript("document.querySelector('[data-testid=\"ops-ux-activity-feedback-indeterminate\"]') !== null", true)

View File

@ -74,6 +74,8 @@ function runBaselineCaptureJob(
expect($context['baseline_profile_id'])->toBe((int) $profile->getKey());
expect($context['source_tenant_id'])->toBe((int) $tenant->getKey());
expect($context)->toHaveKey('effective_scope');
expect(data_get($context, 'progress.phase.key'))->toBe('preparing');
expect(data_get($context, 'progress.phase.label'))->toBe('Preparing baseline capture.');
$effectiveScope = is_array($context['effective_scope'] ?? null) ? $context['effective_scope'] : [];
expect($effectiveScope['policy_types'])->toBe(['deviceConfiguration']);
@ -341,6 +343,8 @@ function runBaselineCaptureJob(
$counts = is_array($run->summary_counts) ? $run->summary_counts : [];
expect((int) ($counts['total'] ?? 0))->toBe(3);
expect((int) ($counts['succeeded'] ?? 0))->toBe(3);
expect(data_get($run->context, 'progress.phase.key'))->toBe('finalizing');
expect(data_get($run->context, 'progress.phase.label'))->toBe('Finalizing baseline capture.');
$snapshot = BaselineSnapshot::query()
->where('baseline_profile_id', $profile->getKey())

View File

@ -220,6 +220,8 @@
$context = is_array($run->context) ? $run->context : [];
expect($context['baseline_profile_id'])->toBe((int) $profile->getKey());
expect($context['baseline_snapshot_id'])->toBe((int) $snapshot->getKey());
expect(data_get($context, 'progress.phase.key'))->toBe('preparing');
expect(data_get($context, 'progress.phase.label'))->toBe('Preparing baseline comparison.');
Queue::assertPushed(CompareBaselineToTenantJob::class);
});

View File

@ -163,6 +163,8 @@ public function capture(
expect($run->outcome)->toBe(OperationRunOutcome::PartiallySucceeded->value);
$context = is_array($run->context) ? $run->context : [];
expect(data_get($context, 'progress.phase.key'))->toBe('finalizing');
expect(data_get($context, 'progress.phase.label'))->toBe('Finalizing baseline comparison.');
$token = $context['baseline_compare']['resume_token'] ?? null;
expect($token)->toBeString();

View File

@ -320,7 +320,46 @@
$pageText = preg_replace('/\s+/', ' ', strip_tags($html));
expect($html)->toContain('data-testid="ops-ux-activity-feedback-indeterminate"')
->and($pageText)->toMatch('/Running · .* · Phase progress pending\./')
->and($pageText)->toMatch('/Running · .* · Capturing evidence\./')
->and($html)->not->toContain('role="progressbar"')
->and(strip_tags($html))->not->toContain('processed (');
})->group('ops-ux');
it('renders tenant review composite progress from canonical composite metadata without inventing a counted percentage', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$this->actingAs($user);
Filament::setTenant($tenant, true);
OperationRun::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'user_id' => (int) $user->getKey(),
'type' => 'tenant.review.compose',
'status' => 'running',
'outcome' => 'pending',
'context' => [
'progress' => [
'composite' => [
'label' => 'Review composition is aggregating 3 operations.',
'operation_count' => 3,
'failed_count' => 0,
'partial_count' => 0,
],
],
],
'started_at' => now()->subMinute(),
]);
$component = Livewire::actingAs($user)
->test(BulkOperationProgress::class)
->call('refreshRuns');
$html = html_entity_decode($component->html(), ENT_QUOTES | ENT_HTML5);
$pageText = preg_replace('/\s+/', ' ', strip_tags($html));
expect($html)->toContain('data-testid="ops-ux-activity-feedback-indeterminate"')
->and($pageText)->toMatch('/Running · .* · Review composition is aggregating 3 operations\./')
->and($html)->not->toContain('role="progressbar"')
->and(strip_tags($html))->not->toContain('processed (');
})->group('ops-ux');

View File

@ -18,13 +18,15 @@
Notification::fake();
[$user, $tenant] = createUserWithTenant(role: 'owner');
$snapshot = seedTenantReviewEvidence($tenant);
$snapshot = seedTenantReviewEvidence($tenant, operationRunCount: 3);
$review = app(\App\Services\TenantReviews\TenantReviewService::class)->create($tenant, $snapshot, $user);
$run = OperationRun::query()->findOrFail($review->operation_run_id);
expect($run->type)->toBe(OperationRunType::TenantReviewCompose->value)
->and(OperationCatalog::label((string) $run->type))->toBe('Review composition');
->and(OperationCatalog::label((string) $run->type))->toBe('Review composition')
->and(data_get($run->context, 'progress.composite.operation_count'))->toBe(3)
->and(data_get($run->context, 'progress.composite.label'))->toBe('Review composition is aggregating 3 operations.');
Queue::assertPushed(ComposeTenantReviewJob::class);

View File

@ -129,7 +129,31 @@
expect($progress['capability'])->toBe('phased')
->and($progress['display'])->toBe('indeterminate')
->and($progress['label'])->toBe('Phase progress pending.')
->and($progress['label'])->toBe('Capturing evidence.')
->and($progress['percent'])->toBeNull();
});
it('uses canonical phase metadata when present for phased runs', function (): void {
$run = OperationRun::factory()->create([
'type' => 'baseline_compare',
'status' => 'running',
'outcome' => 'pending',
'context' => [
'progress' => [
'phase' => [
'key' => 'persisting',
'label' => 'Saving comparison results.',
],
],
],
'started_at' => now()->subMinute(),
]);
$progress = OperationRunProgressContract::forRun($run);
expect($progress['capability'])->toBe('phased')
->and($progress['display'])->toBe('indeterminate')
->and($progress['label'])->toBe('Saving comparison results.')
->and($progress['percent'])->toBeNull();
});
@ -149,4 +173,49 @@
->and($progress['display'])->toBe('indeterminate')
->and($progress['label'])->toBe('Composite progress pending.')
->and($progress['percent'])->toBeNull();
});
it('derives a tenant review composite label from aggregate operation truth', function (): void {
$run = OperationRun::factory()->create([
'type' => 'tenant.review.compose',
'status' => 'running',
'outcome' => 'pending',
'summary_counts' => [
'operation_count' => 3,
],
'started_at' => now()->subMinute(),
]);
$progress = OperationRunProgressContract::forRun($run);
expect($progress['capability'])->toBe('composite')
->and($progress['display'])->toBe('indeterminate')
->and($progress['label'])->toBe('Review composition is aggregating 3 operations.')
->and($progress['percent'])->toBeNull();
});
it('uses explicit composite attention hints when present', function (): void {
$run = OperationRun::factory()->create([
'type' => 'tenant.review.compose',
'status' => 'running',
'outcome' => 'pending',
'summary_counts' => [
'operation_count' => 4,
],
'context' => [
'progress' => [
'composite' => [
'label' => 'Review composition is aggregating 4 operations. 1 failed operation currently needs review.',
],
],
],
'started_at' => now()->subMinute(),
]);
$progress = OperationRunProgressContract::forRun($run);
expect($progress['capability'])->toBe('composite')
->and($progress['display'])->toBe('indeterminate')
->and($progress['label'])->toBe('Review composition is aggregating 4 operations. 1 failed operation currently needs review.')
->and($progress['percent'])->toBeNull();
});

View File

@ -1341,18 +1341,33 @@ # TenantPilot Enterprise UI Standards**Status:** Active **Owner:** Product / En
keep all other run families on activity-only, phased, or composite treatment until they persist equally trustworthy progress truth through the shared contract
limit the current non-counted phase or composite contract to these run families only:
- baseline_capture
- baseline_compare
- tenant.review.compose
keep queued rows activity-only even when a planned total exists
keep running rows without trustworthy processed and total counts activity-only or indeterminate
keep summary_counts.succeeded, summary_counts.failed, and summary_counts.skipped outcome-only; they must not silently replace processed as progress truth
degrade repo-real phase-shaped work such as baseline_capture.evidence_capture or baseline_compare.evidence_capture to a phased indeterminate fallback until a later spec introduces trustworthy phase progress truth
for baseline_capture and baseline_compare, use canonical progress.phase metadata with short operator-safe, non-technical labels such as Preparing baseline capture., Capturing evidence., Refreshing comparison evidence., Evaluating baseline drift., Saving baseline snapshot., and Finalizing baseline comparison.
degrade aggregate multi-run or operation_count shaped work to a composite indeterminate fallback until a later spec introduces trustworthy child-progress truth
for tenant.review.compose, use canonical progress.composite metadata only for bounded aggregate status copy such as Review composition is aggregating 3 operations.; this remains composite indeterminate copy, not counted progress, even when operation_count is visible
when canonical phase or composite metadata is absent or malformed, degrade safely to the existing generic phased or composite fallback instead of inventing percentages, strategy detail, provider detail, or raw technical diagnostics in the shell
switch terminal-success rows to success-state copy instead of showing active progress after completion
record these as follow-up work instead of widening the current contract:
- provider health or support-diagnostics progress rollout
- review-pack or evidence-snapshot overlap with other progress specs
- child-run graph persistence or composite child-link expansion
- dashboard cards or workflow-engine generated progress explanations
keep hide or dismiss behavior browser-session-only and re-open the hint when a new run-enqueued event is accepted for the current tenant
It MUST NOT:

View File

@ -0,0 +1,61 @@
# Specification Quality Checklist: OperationRun Phase & Composite Progress v1
**Purpose**: Validate specification completeness, boundedness, and readiness before implementation
**Created**: 2026-05-05
**Feature**: [spec.md](../spec.md)
## Content Quality
- [x] The package stays on one bounded non-counted progress extension over existing `OperationRun` truth instead of widening into a workflow engine, dashboard redesign, child-run graph, or a second progress framework.
- [x] The spec remains product- and behavior-oriented rather than reading like a low-level implementation diff.
- [x] The package explicitly names the repo-real anchors it builds on: `OperationRunProgressContract`, `OperationUxPresenter`, `OperationStatusNormalizer`, current baseline phase context, and `tenant.review.compose` aggregate operation truth.
- [x] Mandatory repo sections for scope, shared-pattern reuse, Ops-UX, testing, proportionality, and candidate rationale are completed.
## Requirement Completeness
- [x] No unresolved clarification markers remain.
- [x] Requirements are testable and bounded to selected current phase/composite run families plus one standards update.
- [x] The package explicitly keeps `processed` and `total` as the only determinate progress source and keeps phase/composite v1 explicitly non-counted.
- [x] The package explicitly forbids fake percentages, raw technical phase labels, new `summary_counts` keys, a child-run graph, and a workflow engine.
- [x] The package explicitly records the candidate narrowing: review-pack and evidence-snapshot overlap remain with Spec 271, while provider health and support diagnostics remain deferred until repo-real queued progress truth exists.
- [x] The package keeps provider, panel, global-search, asset, queue-family, notification-policy, and persistence changes out of scope.
## Candidate Selection Gate
- [x] The selected candidate exists in `docs/product/spec-candidates.md` and is consistent with the broader roadmap direction.
- [x] The active queue is explicitly empty, so this package records itself as a deliberate manual promotion rather than an automatic next-best-prep target.
- [x] Repo verification confirmed `specs/270-operationrun-progress-contract/` is the immediate prerequisite context for this package.
- [x] Repo verification confirmed `specs/271-counted-progress-rollout/` remains the adjacent counted boundary and must not be silently reopened here.
- [x] Repo verification confirmed baseline capture and baseline compare already persist phase-shaped context that currently terminates in generic fallback labels.
- [x] Repo verification confirmed `tenant.review.compose` already has repo-real aggregate operation truth suitable for a bounded composite summary without adding a child-run graph.
- [x] Repo verification confirmed provider health and support diagnostics do not currently expose equivalent queued phase/composite `OperationRun` truth and therefore remain deferred.
## Feature Readiness
- [x] The package reuses current `OperationRun` truth and the current shared progress contract instead of introducing a second lifecycle or persisted projection.
- [x] The package names both the selected in-scope run families and the excluded candidate families.
- [x] The package forbids new panel, provider, global-search, asset-registration, dashboard, workflow-engine, and persistence changes.
- [x] The package preserves the current polling posture, current terminal-outcome contract, and current counted precedence.
- [x] The planned validation commands stay consistent across `spec.md`, `plan.md`, and `tasks.md`.
- [x] No application implementation was performed while preparing this package.
## Test Governance
- [x] Planned proof stays bounded to existing Unit plus Feature families for Ops-UX, Baselines, TenantReview, and current run-detail compatibility.
- [x] No new heavy-governance or browser family is introduced by default.
- [x] Fixture growth remains bounded to current tenant context helpers, current baseline helpers, current `OperationRun` factories, and current tenant-review fixtures.
- [x] The review outcome, workflow outcome, and test-governance outcome are carried into the active prep package.
## Notes
- Reviewed against `.specify/memory/constitution.md`, `.specify/templates/checklist-template.md`, `docs/product/spec-candidates.md`, `docs/product/roadmap.md`, `specs/268-operationrun-activity-feedback/spec.md`, `specs/270-operationrun-progress-contract/spec.md`, `specs/271-counted-progress-rollout/spec.md`, `apps/platform/app/Support/OpsUx/OperationRunProgressContract.php`, `apps/platform/app/Support/OpsUx/OperationUxPresenter.php`, `apps/platform/app/Support/OpsUx/OperationStatusNormalizer.php`, `apps/platform/app/Services/Baselines/BaselineCaptureService.php`, `apps/platform/app/Jobs/CaptureBaselineSnapshotJob.php`, `apps/platform/app/Jobs/CompareBaselineToTenantJob.php`, `apps/platform/app/Services/TenantReviews/TenantReviewService.php`, `apps/platform/app/Jobs/ComposeTenantReviewJob.php`, `apps/platform/app/Services/TenantReviews/TenantReviewSectionFactory.php`, and `docs/ui/tenantpilot-enterprise-ui-standards.md` on 2026-05-05.
- This checklist is the prep-time outcome record. If implementation widens into provider health/support progress, review-pack overlap, dashboard work, child-run graph persistence, or a workflow engine, the workflow outcome must change before merge.
- No application implementation was performed while preparing this package.
## Review Outcome
- **Outcome class**: `acceptable-special-case`
- **Workflow outcome**: `keep`
- **Test-governance outcome**: `keep`
- **Reason**: the package is a bounded manual-promotion follow-up to Spec 270 that reuses current shared truth, preserves the counted boundary from Spec 271, and explicitly documents the candidate narrowing required by current repo reality.
- **Final note location**: This checklist during prep, and the active feature PR close-out entry only if implementation later forces `split` or `document-in-feature`.

View File

@ -0,0 +1,260 @@
# Implementation Plan: OperationRun Phase & Composite Progress v1
**Branch**: `272-operationrun-phase-composite-progress` | **Date**: 2026-05-05 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/272-operationrun-phase-composite-progress/spec.md`
## Summary
This plan prepares the bounded non-counted follow-up to Spec 270 and the adjacent counted boundary from Spec 271. The implementation path is to reuse `OperationRunProgressContract`, `OperationUxPresenter`, and `OperationRunService`, add canonical operator-safe phase metadata for `baseline_capture` and `baseline_compare`, and add one bounded composite summary path for `tenant.review.compose` from existing aggregate operation truth. The slice must not invent fake percentages, reopen counted rollout work, add a workflow engine, create a child-run graph, or widen into provider health, support diagnostics, or dashboard work.
## Inherited Baseline / Explicit Delta
### Inherited baseline
- `App\Support\OpsUx\OperationRunProgressContract` already centralizes `none`, `activity`, `counted`, `phased`, and `composite` progress modes.
- The current contract already classifies baseline evidence-capture context as `phased` and aggregate multi-run truth as `composite`, but it currently returns only generic placeholder labels.
- `App\Jobs\CaptureBaselineSnapshotJob` and `App\Jobs\CompareBaselineToTenantJob` already persist repo-real baseline phase-shaped context, including eligibility and evidence-capture stats.
- `App\Services\TenantReviews\TenantReviewService` and `App\Jobs\ComposeTenantReviewJob` already create the canonical `tenant.review.compose` run type, and current evidence snapshot summaries already contain aggregate operation counts used later in tenant review composition.
- The current tenant shell adopter already renders generic phased/composite fallbacks through the shared contract without inventing a percentage.
- Spec 271 already owns truthful counted rollout for stable-unit families and must remain distinct from this slice.
### Explicit delta in this plan
- add canonical operator-safe phase metadata and label derivation for `baseline_capture` and `baseline_compare`
- add one bounded composite summary path for `tenant.review.compose` from current aggregate operation truth
- keep counted, terminal, and generic activity semantics unchanged except for consuming more truthful non-counted detail
- document the narrowed candidate boundary explicitly: review-pack and evidence-snapshot overlap remain with Spec 271, while provider health and support diagnostics remain deferred until repo-real queued progress truth exists
## Technical Context
**Language/Version**: PHP 8.4, Laravel 12, Filament v5, Livewire v4
**Primary Dependencies**: existing Ops-UX contract/presenter classes, current baseline services/jobs, current tenant-review services/jobs, Pest v4
**Storage**: PostgreSQL via existing `operation_runs`, baseline, evidence snapshot, and tenant review tables
**Testing**: Pest Unit + Feature
**Validation Lanes**: fast-feedback, confidence
**Target Platform**: existing Laravel monolith in `apps/platform`
**Project Type**: web application (Laravel monolith with Filament)
**Performance Goals**: no new poller family, no new query family, and no slower-than-current activity feedback disclosure
**Constraints**: no fake percentages, no new `summary_counts` keys, no new persistence, no child-run graph, no dashboard redesign, and no provider-health or support-diagnostics rollout
**Scale/Scope**: one shared contract extension across 3 selected run families plus one UI standards update and focused regression coverage
## Likely Affected Repo Surfaces
- `apps/platform/app/Support/OpsUx/OperationRunProgressContract.php`
- `apps/platform/app/Support/OpsUx/OperationUxPresenter.php`
- `apps/platform/app/Support/OpsUx/OperationStatusNormalizer.php`
- `apps/platform/app/Services/OperationRunService.php`
- `apps/platform/app/Services/Baselines/BaselineCaptureService.php`
- `apps/platform/app/Jobs/CaptureBaselineSnapshotJob.php`
- `apps/platform/app/Jobs/CompareBaselineToTenantJob.php`
- `apps/platform/app/Services/TenantReviews/TenantReviewService.php`
- `apps/platform/app/Jobs/ComposeTenantReviewJob.php`
- `apps/platform/app/Services/TenantReviews/TenantReviewSectionFactory.php`
- `apps/platform/tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php`
- `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php`
- `apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php`
- `apps/platform/tests/Feature/BaselineDriftEngine/CaptureBaselineContentTest.php`
- `apps/platform/tests/Feature/Baselines/BaselineCaptureTest.php`
- `apps/platform/tests/Feature/Baselines/BaselineCompareExecutionGuardTest.php`
- `apps/platform/tests/Feature/Baselines/BaselineCompareResumeTokenTest.php`
- `apps/platform/tests/Feature/TenantReview/TenantReviewOperationsUxTest.php`
- `apps/platform/tests/Feature/Filament/OperationRunBaselineTruthSurfaceTest.php`
- `apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php`
- `apps/platform/tests/Feature/TenantReview/TenantReviewRbacTest.php`
- `apps/platform/tests/Feature/Baselines/BaselineProfileAuthorizationTest.php`
- `docs/ui/tenantpilot-enterprise-ui-standards.md`
## UI / Filament & Livewire Fit
- The visible adopter remains the existing tenant-shell activity feedback surface. No new page, dashboard card, widget family, or top-level monitoring surface is introduced.
- Canonical Operations collection/detail routes remain diagnostics-first drill-through targets. They may inherit safer phase/composite disclosure through the shared presenter path, but they do not gain a second progress framework.
- The shell remains decision-first. This slice only improves whether selected runs can describe truthful non-counted phase/composite state.
- Filament stays v5 on Livewire v4. Provider registration remains unchanged in `apps/platform/bootstrap/providers.php`.
## RBAC / Policy Fit
- Existing capability gates for baseline capture, baseline compare, and tenant review remain unchanged.
- Existing `OperationRun` policies remain the only progress-visibility gate.
- No new mutation surface is introduced, so current server-side authorization and confirmation behavior stays on the existing launch/detail surfaces.
## Audit / Logging Fit
- Existing queued toasts and terminal notifications remain authoritative and unchanged.
- Existing audit ownership remains the only audit trail for the covered runs; no new run-local audit channel is introduced.
- Progress detail remains derived execution truth inside the current `OperationRun` ownership model, not a second audit or event stream.
## Data & Query Fit
- Progress truth remains fully derived from existing `operation_runs.context`, `operation_runs.summary_counts`, and current contract logic.
- Selected phase metadata stays inside existing `context` JSON and does not create a new persisted entity.
- Composite progress for `tenant.review.compose` stays bounded to currently available aggregate operation truth from evidence snapshot or review summary data.
- No migration, no new JSON table, no cache layer, and no new persisted user preference or progress mode are planned.
## UI / Surface Guardrail Plan
- **Guardrail scope**: changed surfaces
- **Native vs custom classification summary**: native Filament + existing Livewire/Blade shell surface
- **Shared-family relevance**: Ops-UX activity feedback and current run summaries
- **State layers in scope**: shell, detail compatibility
- **Audience modes in scope**: operator-MSP
- **Decision/diagnostic/raw hierarchy plan**: decision-first on shell, diagnostics-second on Operations detail
- **Raw/support gating plan**: unchanged; raw/support detail remains on diagnostics surfaces only
- **One-primary-action / duplicate-truth control**: keep one dominant `View operation` action and one shared progress contract that now supplies richer non-counted labels
- **Handling modes by drift class or surface**: review-mandatory
- **Repository-signal treatment**: review-mandatory
- **Special surface test profiles**: global-context-shell
- **Required tests or manual smoke**: functional-core, state-contract
- **Exception path and spread control**: none planned
- **Active feature PR close-out entry**: Guardrail / Smoke Coverage
## Shared Pattern & System Fit
- **Cross-cutting feature marker**: yes
- **Systems touched**: Ops-UX progress contract, Ops-UX presenter, baseline jobs/services, tenant-review queue/composition path, shell activity feedback
- **Shared abstractions reused**: `OperationRunProgressContract`, `OperationUxPresenter`, `OperationStatusNormalizer`, `OperationRunService`
- **New abstraction introduced? why?**: no new top-level abstraction planned; extend the current contract/presenter path only
- **Why the existing abstraction was sufficient or insufficient**: classification already exists; what is missing is canonical operator-safe phase/composite detail for selected run families
- **Bounded deviation / spread control**: selected families only; any additional run family without repo-real truthful phase/composite detail stays on current generic fallback
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: yes, for active progress meaning only
- **Central contract reused**: existing OperationRun Start UX Contract plus `OperationRunProgressContract`
- **Delegated UX behaviors**: queued toast, canonical run links, `run-enqueued` event, and terminal-notification lifecycle remain delegated and unchanged
- **Surface-owned behavior kept local**: current launch inputs, detailed diagnostics, and domain-specific result explanations stay local to their current surfaces
- **Queued DB-notification policy**: `N/A` - unchanged
- **Terminal notification path**: unchanged central lifecycle mechanism
- **Exception path**: none
## Provider Boundary & Portability Fit
- **Shared provider/platform boundary touched?**: no
- **Provider-owned seams**: `N/A`
- **Platform-core seams**: `OperationRun` truth, Ops-UX contract/presenter, baseline and tenant-review execution truth
- **Neutral platform terms / contracts preserved**: `Operation`, `activity`, `phase progress`, `composite progress`, `terminal outcome`
- **Retained provider-specific semantics and why**: none
- **Bounded extraction or follow-up path**: provider health and support diagnostics remain an explicit later follow-up if queued progress truth exists later
## Constitution Check
*GATE: Must pass before implementation begins and again before merge.*
- Inventory-first: PASS. The slice only enriches current execution feedback over existing operational truth.
- Read/write separation: PASS. No new external write path is introduced; current domain jobs keep their existing product responsibilities and only persist better progress detail in `OperationRun` context.
- Graph contract path: PASS. No new Graph or provider contract is introduced.
- Deterministic capabilities: PASS. Authorization and run visibility remain deterministic and testable.
- RBAC-UX: PASS. Visibility remains on existing tenant/admin boundaries and current `OperationRun` policies.
- Run observability: PASS. Long-running work still flows through current `OperationRun` ownership and current Ops-UX surfaces.
- Ops-UX lifecycle: PASS. `status` and `outcome` ownership remains on `OperationRunService`; this slice only adds richer non-counted detail.
- Ops-UX summary counts: PASS. No new `summary_counts` key is planned.
- Test governance: PASS. Proof remains bounded to Unit plus Feature.
- Proportionality / no premature abstraction: PASS. No new workflow engine, child-run graph, or second presenter path is introduced.
- Persisted truth / behavioral state: PASS. No new table, cache, or lifecycle family is added.
- Shared pattern first / UI semantics / Filament-native UI: PASS. Existing Ops-UX path remains central and the visible adopter is unchanged.
- Provider boundary: PASS. No provider/platform seam change.
- Filament/Laravel panel safety: PASS. Filament v5 stays on Livewire v4, provider registration remains in `apps/platform/bootstrap/providers.php`, and no assets change.
**Gate evaluation**: PASS.
## Test Governance Check
- **Test purpose / classification by changed surface**: Unit for contract precedence and label derivation; Feature for selected baseline and tenant-review writer seams plus current shell adoption
- **Affected validation lanes**: fast-feedback, confidence
- **Why this lane mix is the narrowest sufficient proof**: the shared contract already has a focused unit suite, and the selected run families already have domain feature suites that can prove current context/summary truth without adding new browser or heavy-governance obligations
- **Narrowest proving command(s)**:
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php tests/Feature/BaselineDriftEngine/CaptureBaselineContentTest.php tests/Feature/Baselines/BaselineCaptureTest.php tests/Feature/Baselines/BaselineCompareResumeTokenTest.php tests/Feature/Baselines/BaselineCompareExecutionGuardTest.php tests/Feature/TenantReview/TenantReviewOperationsUxTest.php tests/Feature/Filament/OperationRunBaselineTruthSurfaceTest.php tests/Feature/Operations/TenantlessOperationRunViewerTest.php tests/Feature/TenantReview/TenantReviewRbacTest.php tests/Feature/Baselines/BaselineProfileAuthorizationTest.php`
- **Fixture / helper / factory / seed / context cost risks**: low to moderate; reuse current tenant context helpers, existing baseline helpers, and current tenant-review fixtures instead of adding a new provider-heavy harness
- **Expensive defaults or shared helper growth introduced?**: no
- **Heavy-family additions, promotions, or visibility changes**: none
- **Surface-class relief / special coverage rule**: `global-context-shell`
- **Closing validation and reviewer handoff**: rerun the two proving commands above and verify that selected run families now expose truthful non-counted detail, the full precedence chain `phased > composite > counted > activity` still holds, counted semantics stay counted-only, malformed metadata degrades safely, current `404`/`403` visibility semantics remain intact, no new polling loop was introduced, and excluded families remain excluded
- **Budget / baseline / trend follow-up**: none expected beyond a small feature-local increase
- **Review-stop questions**: did any run family leak raw technical phase labels, did any composite summary masquerade as a percentage, did any excluded family sneak in, and did any new `summary_counts` key or workflow framework appear?
- **Escalation path**: `reject-or-split` for any provider-health/support expansion, dashboard work, child-run graph persistence, or workflow-engine drift
- **Active feature PR close-out entry**: Guardrail / Smoke Coverage
- **Why no dedicated follow-up spec is needed**: this package is itself the bounded non-counted progress follow-up to Spec 270; remaining excluded ideas are already named as explicit later follow-ups.
## Project Structure
### Documentation (this feature)
```text
specs/272-operationrun-phase-composite-progress/
├── spec.md
├── plan.md
├── tasks.md
└── checklists/
└── requirements.md
```
### Source Code (expected implementation surfaces)
```text
apps/platform/app/Support/OpsUx/
apps/platform/app/Services/OperationRunService.php
apps/platform/app/Services/Baselines/BaselineCaptureService.php
apps/platform/app/Jobs/CaptureBaselineSnapshotJob.php
apps/platform/app/Jobs/CompareBaselineToTenantJob.php
apps/platform/app/Services/TenantReviews/TenantReviewService.php
apps/platform/app/Jobs/ComposeTenantReviewJob.php
apps/platform/app/Services/TenantReviews/TenantReviewSectionFactory.php
apps/platform/tests/Unit/Support/OpsUx/
apps/platform/tests/Feature/OpsUx/
apps/platform/tests/Feature/Baselines/
apps/platform/tests/Feature/TenantReview/
apps/platform/tests/Feature/Filament/
docs/ui/tenantpilot-enterprise-ui-standards.md
```
**Structure Decision**: keep the rollout local to current jobs/services plus the existing Ops-UX support family. Do not introduce a workflow engine, child-run graph, or second progress framework.
## Data / Migration Implications
- No migration or schema change is planned.
- No new persisted progress mode or preference is allowed.
- Selected phase/composite detail remains inside existing `operation_runs.context` JSON only.
- No backfill is planned. Historical runs remain historical truth; the rollout affects future execution and active-run rendering only.
## Rollout Considerations
- Filament remains v5 on Livewire v4. Provider registration remains in `apps/platform/bootstrap/providers.php`.
- No global search or asset change is required because the slice changes only current run-progress truth.
- No destructive action or confirmation model changes are planned.
- No deployment step beyond ordinary code deploy, focused tests, and current formatting validation is expected.
## Risk Controls
- Reject any implementation that turns phase/composite labels into fake percentages or a synthetic progress bar.
- Reject any implementation that leaks raw technical step names, exception class names, or transport details into the default operator-facing progress label.
- Reject any implementation that adds a child-run graph or persists `child_run_ids` as a new framework instead of a bounded current-release need.
- Reject any implementation that widens the rollout to provider health, support diagnostics, review-pack, or evidence-snapshot overlap without an explicit follow-up spec.
- Reject any implementation that changes counted precedence or adds a new `summary_counts` key to compensate for missing phase/composite truth.
## Implementation Phases
### Phase 0 - Confirm The Selected Non-Counted Seams
- Verify the current phase and composite fallback behavior in `OperationRunProgressContract`, the current shell adopter, the current baseline jobs, and the current tenant-review queue/composition path.
- Reconfirm that provider health, support diagnostics, review-pack overlap, and child-run graph persistence remain out of scope.
### Phase 1 - Roll Out Baseline Phase Metadata
- Add canonical operator-safe phase metadata for `baseline_capture` and `baseline_compare` over the repo-real lifecycle boundaries that already exist today.
### Phase 2 - Roll Out Tenant Review Composite Summary
- Seed a bounded composite summary for `tenant.review.compose` from current evidence-basis operation truth and keep it explicitly non-counted.
### Phase 3 - Lock The Guardrail And Proof
- Update the UI standards and focused tests so later non-counted progress work extends one contract rather than inventing new semantics.
## Proportionality Review
- **Current operator problem**: current non-counted runs know more than the product currently says, yet the shell still shows vague generic fallback copy.
- **Existing structure is insufficient because**: the shared contract can classify `phased` and `composite`, but it cannot yet explain them with canonical operator-safe detail for selected run families.
- **Narrowest correct implementation**: update only selected repo-real phase/composite families and keep all other work on current generic fallback or counted semantics.
- **Ownership cost created**: selected context writes in existing jobs/services, focused tests, and one standards update.
- **Alternative intentionally rejected**: a workflow engine, child-run graph, or fake percentage model was rejected because all would be structurally heavier and less truthful than the current-release need.
- **Release truth**: current-release truth. The repo already contains the contract, the shell adopter, the baseline phase hints, and the tenant-review aggregate truth needed for this rollout.

View File

@ -0,0 +1,310 @@
# Feature Specification: OperationRun Phase & Composite Progress v1
**Feature Branch**: `272-operationrun-phase-composite-progress`
**Created**: 2026-05-05
**Status**: Ready for implementation
**Input**: Manual promotion from `docs/product/spec-candidates.md` after the repo-based next-best-prep review confirmed the automatic queue is intentionally empty and the user explicitly chose to promote candidate `272`.
## Spec Candidate Check *(mandatory - SPEC-GATE-001)*
- **Problem**: `specs/270-operationrun-progress-contract/` already introduced truthful `none`, `activity`, `counted`, `phased`, and `composite` progress capabilities, but the repo still treats `phased` and `composite` as generic fallbacks. Baseline capture and baseline compare already persist phase-shaped context, and `tenant.review.compose` already carries aggregate operation truth, yet operators still only get vague `Phase progress pending.` or `Composite progress pending.` copy.
- **Today's failure**: long-running, non-countable, or aggregate runs can expose meaningful execution truth today, but the shared contract cannot translate that truth into operator-safe phase text or composite summaries. That leaves enterprise operators with either vague indeterminate copy or pressure to invent fake percentages and local explanations.
- **User-visible improvement**: selected non-countable `OperationRun` families show truthful operator-safe phase text or composite child summaries while remaining explicitly non-counted. Operators can tell what the system is doing without a fabricated percentage.
- **Smallest enterprise-capable version**: extend the existing shared Ops-UX progress contract and current shell adopter so `baseline_capture`, `baseline_compare`, and `tenant.review.compose` can surface canonical non-counted phase/composite labels from existing `OperationRun.context` and existing aggregate summary truth, without adding a workflow engine, a dashboard redesign, or a second presenter layer.
- **Explicit non-goals**: no full workflow engine, no new top-level monitoring page, no child-run graph persistence, no provider health or support-diagnostics rollout unless repo-real queued progress truth appears first, no review-pack or evidence-snapshot overlap with Spec 271, no fake percentages, no AI-generated progress explanations, no new `summary_counts` keys, and no new `OperationRun` lifecycle state.
- **Permanent complexity imported**: one bounded non-counted progress detail extension inside the existing Ops-UX contract, one derived phase-step vocabulary kept in code and docs only, selected context-shape updates for repo-real run families, focused Pest Unit plus Feature coverage, and one UI standards update.
- **Why now**: Spec 270 already prepared the shared contract and Spec 271 already claims the counted rollout boundary. The next truthful gap is the current generic `phased` and `composite` fallback behavior. Repo truth already contains real baseline phase context and tenant-review aggregate counts, so the product can now make those categories honest and useful without inventing a new framework.
- **Why not local**: adding baseline-only or tenant-review-only labels would fragment progress semantics across the shell, Operations detail, and future non-counted run families. The truth gap is shared, so the fix must stay on the shared contract.
- **Approval class**: Core Enterprise
- **Red flags triggered**: derived phase vocabulary, selected persisted context metadata for non-counted progress, and one shared contract extension. Defense: the vocabulary stays derived only, persistence stays inside the existing `operation_runs.context` JSON, and the feature reuses the current Ops-UX contract rather than adding a second framework.
- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 1 | Komplexitaet: 1 | Produktnaehe: 2 | Wiederverwendung: 2 | **Gesamt: 10/12**
- **Decision**: approve
## Spec Scope Fields *(mandatory)*
- **Scope**: tenant + canonical-view
- **Primary Routes**:
- `/admin/t/{tenant}/...` tenant-scoped launch surfaces whose existing shell feedback adopts the shared progress contract
- `/admin/t/{tenant}` remains the contextual surface where recent-operation activity hints stay visible; no new tenant dashboard card is introduced
- `/admin/operations` and `/admin/operations/{run}` remain the canonical collection/detail routes that must stay compatible with the resulting phase/composite truth
- **Data Ownership**: existing `operation_runs.context`, `operation_runs.summary_counts`, `operation_runs.status`, and `operation_runs.outcome` remain the only persisted execution truth. This feature may standardize new phase/composite context payloads for selected run families inside `context`, but it must not add a table, cache, mirror projection, or persisted progress-mode flag.
- **RBAC**: existing capability checks for baseline capture, baseline compare, and tenant review continue to govern launch access. Existing `OperationRun` policies remain the only visibility gate for phase/composite progress feedback.
For canonical-view behavior:
- **Default filter behavior when tenant-context is active**: unchanged. The shell continues to show only the current tenant's visible runs, and canonical Operations collection/detail access remains tenant-aware when tenant context is active.
- **Explicit entitlement checks preventing cross-tenant leakage**: unchanged. Non-members remain `404`, member-but-missing-capability remains `403`, and no selected run family may emit phase/composite copy for a run the actor cannot already view.
## Cross-Cutting / Shared Pattern Reuse *(mandatory)*
- **Cross-cutting feature?**: yes
- **Interaction class(es)**: status messaging, activity feedback, execution-truth summaries, and canonical operation guidance
- **Systems touched**: `OperationRunProgressContract`, `OperationUxPresenter`, `OperationStatusNormalizer`, current shell activity feedback, baseline capture/compare jobs, tenant-review composition start/queue path, and `docs/ui/tenantpilot-enterprise-ui-standards.md`
- **Existing pattern(s) to extend**: Spec 270 shared progress contract, current Ops-UX 3-surface lifecycle rules, current shell feedback host, and current baseline/tenant-review execution truth
- **Shared contract / presenter / builder / renderer to reuse**: `App\Support\OpsUx\OperationRunProgressContract`, `App\Support\OpsUx\OperationUxPresenter`, `App\Support\OpsUx\OperationStatusNormalizer`, `App\Services\OperationRunService`, and the existing shell activity feedback surface
- **Why the existing shared path is sufficient or insufficient**: the repo already has one shared progress contract and one shared presenter path, but `phased` and `composite` currently stop at generic placeholder labels. The gap is not classification; it is the lack of canonical operator-safe detail for selected non-counted run families.
- **Allowed deviation and why**: none planned. The slice must extend the shared contract and shared presenter path rather than introducing a baseline-local or tenant-review-local progress helper.
- **Consistency impact**: `preparing`, `fetching`, `processing`, `persisting`, and `finalizing` must remain operator-safe labels rather than raw internals, and composite summaries must never imply a counted percentage unless `processed` and `total` separately satisfy the counted contract.
- **Review focus**: reviewers must block any implementation that leaks raw technical phase names, invents fake percentages, reorders the current progress-contract precedence, or quietly widens the feature to unrelated run families.
## OperationRun UX Impact *(mandatory)*
- **Touches OperationRun start/completion/link UX?**: yes
- **Shared OperationRun UX contract/layer reused**: existing OperationRun Start UX Contract plus `App\Support\OpsUx\OperationRunProgressContract` and `App\Support\OpsUx\OperationUxPresenter`
- **Delegated start/completion UX behaviors**: queued toast wording, canonical `View operation` links, tenant-safe URL resolution, current `run-enqueued` browser events, and existing terminal notifications remain delegated to the current shared path and are unchanged in this slice
- **Local surface-owned behavior that remains**: current baseline and tenant-review launch inputs plus current run-detail diagnostics stay local to their existing surfaces; non-counted progress semantics do not
- **Queued DB-notification policy**: `N/A` - unchanged
- **Terminal notification path**: unchanged central lifecycle mechanism
- **Exception required?**: none
## Provider Boundary / Platform Core Check *(mandatory)*
- **Shared provider/platform boundary touched?**: no
- **Boundary classification**: `N/A`
- **Seams affected**: `N/A`
- **Neutral platform terms preserved or introduced**: `Operation`, `progress`, `phase progress`, `composite progress`, `activity`, `terminal outcome`
- **Provider-specific semantics retained and why**: none
- **Why this does not deepen provider coupling accidentally**: the feature only clarifies shared execution truth over existing platform-owned `OperationRun` data and existing domain jobs
- **Follow-up path**: none
## UI / Surface Guardrail Impact *(mandatory)*
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note |
|---|---|---|---|---|---|---|
| Existing OperationRun activity feedback on the tenant shell | yes | Native Filament + existing Livewire/Blade surface | Ops-UX activity feedback and execution-truth summaries | shell | no | No new surface is introduced; selected runs gain truthful phase/composite detail within the existing host |
## Decision-First Surface Role *(mandatory)*
| Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction |
|---|---|---|---|---|---|---|---|
| Existing OperationRun activity feedback on the tenant shell | Primary Decision Surface | Decide whether current work simply needs time, is in a meaningful phase, or needs review because aggregate child work is failing | operation label, lifecycle state, one truthful non-counted phase/composite label when available, and canonical `View operation` action | full run detail, logs, evidence, and diagnostics stay on Operations detail | Primary because it is the current visible progress host and the first place operators check after starting long-running work | Follows the existing start-surface workflow | Replaces vague generic fallback copy without creating another widget family |
## Audience-Aware Disclosure *(mandatory)*
| Surface | Audience Modes In Scope | Decision-First Default-Visible Content | Operator Diagnostics | Support / Raw Evidence | One Dominant Next Action | Hidden / Gated By Default | Duplicate-Truth Prevention |
|---|---|---|---|---|---|---|---|
| Existing OperationRun activity feedback on the tenant shell | operator-MSP | operation label, lifecycle state, operator-safe phase/composite label, and canonical open link | one concise guidance line only when it changes the next action | raw payloads, failure internals, evidence details, and unrestricted diagnostics | `View operation` | raw/support detail stays on Operations detail | the shell shows only one progress meaning derived from the shared contract rather than per-run-family ad hoc labels |
## UI/UX Surface Classification *(mandatory)*
| Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Existing OperationRun activity feedback on the tenant shell | Monitoring hint | Activity shell hint | Open the most relevant operation if follow-up is needed | explicit `View operation` link | forbidden | overflow navigation only | none | `/admin/operations?tenant_id={currentTenant}` | `/admin/operations/{run}` | current tenant context from the shell | Operation | lifecycle state plus one truthful non-counted progress label | none |
## Operator Surface Contract *(mandatory)*
| Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions |
|---|---|---|---|---|---|---|---|---|---|---|
| Existing OperationRun activity feedback on the tenant shell | Tenant operator | Decide whether active work is in a meaningful phase, an aggregate child-review state, or merely generic activity | Start-surface hint | What is this operation doing right now, and do I need to act? | operation label, lifecycle state, operator-safe phase/composite label, canonical open link | detailed run diagnostics and evidence on Operations pages | lifecycle, progress capability | none | `View operation`, `Show all operations` | none |
## Proportionality Review *(mandatory when structural complexity is introduced)*
- **New source of truth?**: no
- **New persisted entity/table/artifact?**: no
- **New abstraction?**: no new top-level abstraction by default; the slice extends the existing progress contract and presenter path only
- **New enum/state/reason family?**: yes - one derived phase-step vocabulary and one bounded composite-summary shape, both kept in code and docs only
- **New cross-domain UI framework/taxonomy?**: no
- **Current operator problem**: selected non-countable or aggregate runs already carry more truthful execution detail than the product surfaces today, but that truth is trapped in raw context or generic placeholders
- **Existing structure is insufficient because**: the current generic phased/composite fallback tells operators only that some truth exists, not what is actually happening or why a run still deserves patience versus review
- **Narrowest correct implementation**: extend the existing shared progress contract and selected existing jobs/services so only repo-real phase/composite families gain operator-safe non-counted detail
- **Ownership cost**: selected context metadata writes in current jobs/services, focused Unit plus Feature coverage, and one UI standards update
- **Alternative intentionally rejected**: forcing these runs into counted percentages or introducing a workflow engine was rejected because both options would be either dishonest or structurally disproportionate
- **Release truth**: current-release truth. The repo already contains phase-shaped baseline context and aggregate tenant-review operation truth that justify this bounded extension now.
### Compatibility posture
This feature assumes a pre-production environment.
Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by this spec.
Canonical replacement of vague phase/composite fallback copy is preferred over preserving duplicate local semantics.
## Testing / Lane / Runtime Impact *(mandatory)*
- **Test purpose / classification**: Unit, Feature
- **Validation lane(s)**: fast-feedback, confidence
- **Why this classification and these lanes are sufficient**: the slice changes shared render semantics plus current job/service metadata writes. One focused Unit suite can prove capability precedence and label derivation cheaply, while focused Feature suites can prove the selected baseline and tenant-review seams persist truthful non-counted detail and the existing shell host renders it without inventing a percentage.
- **New or expanded test families**: extend `tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php`, `tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php`, `tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php`, `tests/Feature/Baselines/BaselineCaptureTest.php`, `tests/Feature/BaselineDriftEngine/CaptureBaselineContentTest.php`, `tests/Feature/Baselines/BaselineCompareResumeTokenTest.php`, `tests/Feature/Baselines/BaselineCompareExecutionGuardTest.php`, and `tests/Feature/TenantReview/TenantReviewOperationsUxTest.php`; extend `tests/Feature/Filament/OperationRunBaselineTruthSurfaceTest.php`, `tests/Feature/Operations/TenantlessOperationRunViewerTest.php`, `tests/Feature/TenantReview/TenantReviewRbacTest.php`, and `tests/Feature/Baselines/BaselineProfileAuthorizationTest.php` only as needed when presenter or authorization compatibility proof changes require it
- **Fixture / helper cost impact**: low to moderate. Reuse existing `OperationRun` factories, baseline helpers, tenant-review fixtures, and current tenant context helpers; do not add a new browser family or provider-heavy test harness
- **Heavy-family visibility / justification**: none
- **Special surface test profile**: global-context-shell
- **Standard-native relief or required special coverage**: ordinary Unit plus Feature coverage only. Browser proof remains out of scope because layout and clickability are already owned by prior Ops-UX specs.
- **Reviewer handoff**: reviewers must confirm that phase/composite labels are operator-safe, the full precedence chain `phased > composite > counted > activity` still holds, counted progress remains gated strictly by `processed` plus `total`, malformed or missing metadata degrades safely, current `404`/`403` visibility semantics remain intact, and excluded run families remain excluded
- **Budget / baseline / trend impact**: small feature-local increase only
- **Escalation needed**: `reject-or-split` if implementation widens into provider health or support-diagnostics rollout, dashboard work, child-run graph persistence, or a new workflow engine
- **Active feature PR close-out entry**: Guardrail / Smoke Coverage
- **Planned validation commands**:
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php tests/Feature/BaselineDriftEngine/CaptureBaselineContentTest.php tests/Feature/Baselines/BaselineCaptureTest.php tests/Feature/Baselines/BaselineCompareResumeTokenTest.php tests/Feature/Baselines/BaselineCompareExecutionGuardTest.php tests/Feature/TenantReview/TenantReviewOperationsUxTest.php tests/Feature/Filament/OperationRunBaselineTruthSurfaceTest.php tests/Feature/Operations/TenantlessOperationRunViewerTest.php tests/Feature/TenantReview/TenantReviewRbacTest.php tests/Feature/Baselines/BaselineProfileAuthorizationTest.php`
## User Scenarios & Testing *(mandatory)*
### User Story 1 - See truthful phase progress for baseline capture (Priority: P1)
As a tenant operator, I need baseline capture to tell me which major phase is active without showing a fake percentage, so I can tell whether the run is still preparing, capturing evidence, persisting a snapshot, or truly stuck.
**Why this priority**: baseline capture is already a repo-real phase candidate because it persists preflight/runtime eligibility context and full-content evidence-capture stats today.
**Independent Test**: start a baseline capture through the current service and job path, drive both standard and full-content paths, and verify that active runs expose truthful operator-safe phase labels without rendering a determinate progress bar.
**Acceptance Scenarios**:
1. **Given** a baseline capture run is queued and the runtime eligibility recheck begins, **When** the run becomes active, **Then** the shared contract exposes an operator-safe preparation or fetch phase instead of generic pending copy.
2. **Given** full-content evidence capture is underway, **When** the run reports evidence-capture stats, **Then** the shell shows a phase label for the current work and does not render a counted progress bar unless `processed` and `total` separately satisfy counted semantics.
3. **Given** the run blocks, fails, or completes, **When** the run becomes terminal, **Then** terminal outcome remains authoritative and the progress host no longer behaves like active phase progress.
---
### User Story 2 - See truthful phase progress for baseline compare (Priority: P1)
As a tenant operator, I need baseline compare to expose which major phase is active, so I can distinguish scope preparation, evidence capture, compare execution, and finalization without guessing from a generic running label.
**Why this priority**: baseline compare already carries repo-real evidence-capture phase stats and resume-token context, which makes it the second clearest non-counted rollout target.
**Independent Test**: queue a baseline compare that exercises preparation, optional evidence capture, and completion or partial-completion paths, then verify that the run exposes operator-safe phase labels and still avoids counted rendering when only phase truth exists.
**Acceptance Scenarios**:
1. **Given** a baseline compare begins with full-content capture enabled, **When** evidence capture or resume processing is active, **Then** the shared contract exposes an operator-safe phase label rather than a generic composite or counted label.
2. **Given** compare execution moves from preparation to drift evaluation and persistence, **When** the job updates `OperationRun.context`, **Then** the run shows the current phase truthfully and remains explicitly non-counted.
3. **Given** the compare ends partially succeeded, blocked, or failed, **When** the run becomes terminal, **Then** the product drops active phase treatment and keeps terminal outcome plus artifact-truth semantics separate.
---
### User Story 3 - See truthful composite progress for tenant review compose (Priority: P2)
As a tenant operator, I need tenant review composition to summarize the child operation posture without pretending it is percentage-complete, so I can tell whether the review is aggregating healthy or problematic recent operations before I drill into details.
**Why this priority**: `tenant.review.compose` already has a canonical run type and repo-real aggregate operation truth through evidence-snapshot sections and review summary data, so it is a bounded composite target without inventing a child-run graph.
**Independent Test**: create or refresh a tenant review from an evidence snapshot with known operations summary counts, then verify that the active run exposes a composite summary with child counts and failure hints while staying non-counted.
**Acceptance Scenarios**:
1. **Given** tenant review composition is queued from an evidence snapshot with recent operations summary counts, **When** the run starts, **Then** the shared contract exposes a composite summary that includes the aggregate operation count without rendering a progress bar.
2. **Given** the current evidence basis already indicates failed or partial recent operations, **When** the run is active, **Then** the composite summary includes an operator-safe child-state or next-step hint rather than generic `Composite progress pending.` copy.
3. **Given** the review composition finishes, **When** the run becomes terminal, **Then** terminal review outcome remains separate from composite progress and no active composite treatment remains.
---
### User Story 4 - Preserve non-counted progress boundaries in docs and review artifacts (Priority: P2)
As a maintainer, I need the package and UI standards to say exactly which runs can claim phase or composite progress and which cannot, so later work does not reopen fake percentages or broaden this slice implicitly.
**Why this priority**: the value of phase/composite v1 depends on keeping the counted, phased, and composite boundaries explicit after Specs 270 and 271.
**Independent Test**: review the updated package and UI standards together, then confirm that excluded candidate ideas remain named follow-ups instead of hidden implementation obligations.
**Acceptance Scenarios**:
1. **Given** a maintainer reads this package after implementation, **When** they review the standards and requirements, **Then** they can tell that `processed` and `total` remain the only determinate progress source and that phase/composite v1 is explicitly non-counted.
2. **Given** the original candidate wording mentioned provider health, support diagnostics, review packs, and evidence snapshots, **When** a maintainer reviews the final package, **Then** they can see why those areas stayed outside this slice under current repo truth.
### Edge Cases
- Missing or malformed phase/composite metadata must degrade safely to the current generic `phased`, `composite`, or `activity` behavior rather than failing the host surface.
- Counted truth must remain subordinate to current precedence: when selected phase or composite metadata is present, the host must not fall back to a percentage just because `summary_counts` also contains numbers.
- Terminal runs may retain phase/composite context for diagnostics, but active progress treatment must stop as soon as the run is no longer currently active.
- `tenant.review.compose` with zero or missing operations summary data must degrade to generic activity or minimal composite disclosure rather than inventing a child-state summary.
- Provider health and support diagnostics remain out of scope unless a later feature establishes repo-real queued progress truth for those surfaces.
## Requirements *(mandatory)*
**Constitution alignment summary**: This feature adds no new Graph contract, no new persisted entity, no new `OperationRun` lifecycle, and no new notification path. It only extends the existing shared progress contract and selected current job/service writers so non-counted runs can expose truthful phase or composite detail.
### Functional Requirements
- **FR-001**: The implementation MUST reuse `App\Support\OpsUx\OperationRunProgressContract`, `App\Support\OpsUx\OperationUxPresenter`, `App\Support\OpsUx\OperationStatusNormalizer`, and `App\Services\OperationRunService` as the only non-counted progress contract and shared presentation path.
- **FR-002**: The in-scope phase run families for v1 are limited to `baseline_capture` and `baseline_compare` under current repo truth.
- **FR-003**: The in-scope composite run family for v1 is limited to `tenant.review.compose` under current repo truth.
- **FR-004**: Selected phase run families MUST persist canonical phase metadata inside the existing `OperationRun.context` payload with a stable machine-friendly phase key and an operator-safe label. Raw implementation internals MUST NOT be surfaced directly to the host surface.
- **FR-005**: The derived phase vocabulary for v1 is limited to operator-safe step families such as `preparing`, `fetching`, `processing`, `persisting`, and `finalizing`. Individual run families may skip phases that do not materially exist in their real workflow, but they MUST NOT invent additional percentage-bearing states.
- **FR-006**: Baseline capture MUST surface truthful phase progress for its current lifecycle boundaries, including current eligibility or preparation work, current subject or snapshot work, optional evidence capture when enabled, persistence, and finalization.
- **FR-007**: Baseline compare MUST surface truthful phase progress for its current lifecycle boundaries, including current coverage or preparation work, optional evidence capture or resume work, compare execution, persistence, and finalization.
- **FR-008**: `tenant.review.compose` MUST surface composite progress from repo-real aggregate truth already available from the current evidence basis or current review summary, including aggregate child count and any current failed or partial child summary or next-step hint that can be derived honestly.
- **FR-009**: Composite progress MUST remain explicitly non-counted. Aggregate child counts, failed-child counts, and partial-child counts MUST NOT become a back-door determinate progress bar unless `processed` and `total` separately satisfy the counted contract.
- **FR-010**: When selected phase/composite metadata is missing or malformed, the shared contract MUST degrade safely to the current generic `phased`, `composite`, or `activity` behavior instead of failing closed or inventing a new state.
- **FR-011**: Current precedence remains unchanged: active `phased` truth wins over `composite`, `composite` wins over counted, counted wins over generic activity, and terminal runs render no active progress treatment.
- **FR-012**: This slice MUST NOT add new `summary_counts` keys, new progress capabilities, a new dashboard card, a child-run graph, a workflow engine, or provider health/support-diagnostics progress behavior.
- **FR-013**: This slice MUST NOT reopen review-pack or evidence-snapshot progress semantics owned by Spec 271 unless current repo truth proves they still need non-counted treatment in a later follow-up spec.
- **FR-014**: The feature MUST update `docs/ui/tenantpilot-enterprise-ui-standards.md` so maintainers can see which current run families may claim phase/composite progress and which families remain activity-only, counted, or deferred.
### Authorization and Safety Requirements
- **AR-001**: Existing tenant/admin-plane authorization remains unchanged: non-members and out-of-scope actors stay `404`, while member-but-missing-capability actors stay `403`.
- **AR-002**: No in-scope surface may reveal phase/composite progress for a run the current actor cannot already view through existing `OperationRun` policy checks.
- **AR-003**: No new destructive or mutating UI action is introduced. Existing baseline and tenant-review start surfaces keep their current authorization and confirmation rules.
### Non-Functional Requirements
- **NFR-001**: Filament remains v5 on Livewire v4. No panel-provider registration change is allowed; `apps/platform/bootstrap/providers.php` remains authoritative.
- **NFR-002**: No new panel, globally searchable resource, or asset-registration strategy is allowed.
- **NFR-003**: No new parallel polling loop is allowed. Existing shell and monitoring pollers remain unchanged.
- **NFR-004**: Operator-safe phase/composite copy must remain concise, domain-safe, and compatible with the current shell density. It must not become a raw technical trace.
- **NFR-005**: The rollout must stay bounded enough that all changed behavior can be proved with focused Unit plus Feature commands rather than a new heavy-governance or browser family.
## Deferred Follow-Ups / Explicit Non-Goals
- provider health or support-diagnostics progress rollout once those surfaces have repo-real queued phase/composite truth
- review-pack or evidence-snapshot non-counted progress treatment if Spec 271 later proves insufficient
- child-run graph persistence through `child_run_ids` or `operation_run_ids`
- `273 - Tenant Dashboard Active Operations Summary Card`
- any full workflow engine, tray redesign, or AI-generated progress explanation layer
## Key Entities
- **Phase Progress Hint**: the canonical operator-safe non-counted progress payload derived from `OperationRun.context` for selected phase-shaped run families.
- **Composite Progress Summary**: the canonical operator-safe aggregate child summary derived from existing current tenant-review operation truth without implying a percentage.
- **Selected Phase Run Family**: a currently repo-real run family, such as baseline capture or baseline compare, whose execution truth is best described by current phases rather than determinate counts.
- **Selected Composite Run Family**: a currently repo-real run family, such as tenant review compose, whose execution truth is best described by aggregate child state rather than determinate counts.
## Success Criteria *(mandatory)*
- **SC-001**: Focused Unit coverage proves that selected phase and composite metadata produce operator-safe non-counted labels while current counted precedence still works only when truthful `processed` and `total` exist.
- **SC-002**: Focused shell Feature coverage proves that baseline capture and baseline compare show meaningful non-counted phase labels without rendering a progress bar in the covered active scenarios.
- **SC-003**: Focused Feature coverage proves that `tenant.review.compose` can expose a truthful composite summary from current aggregate operation truth without rendering a counted percentage.
- **SC-004**: Missing or malformed metadata degrades safely to current generic fallback behavior in 100% of covered scenarios.
- **SC-005**: The UI standards and Spec Kit artifacts document one canonical non-counted progress contract and explicitly keep provider health, support diagnostics, review-pack overlap, dashboard work, and workflow-engine ideas out of this slice.
## Candidate Selection Rationale
- **Selected candidate**: OperationRun Phase & Composite Progress v1
- **Source locations**:
- `docs/product/spec-candidates.md`
- `docs/product/roadmap.md`
- `specs/270-operationrun-progress-contract/spec.md`
- `specs/271-counted-progress-rollout/spec.md`
- `apps/platform/app/Support/OpsUx/OperationRunProgressContract.php`
- `apps/platform/app/Jobs/CaptureBaselineSnapshotJob.php`
- `apps/platform/app/Jobs/CompareBaselineToTenantJob.php`
- `apps/platform/app/Jobs/ComposeTenantReviewJob.php`
- `apps/platform/app/Services/TenantReviews/TenantReviewService.php`
- **Why selected**: the active automatic prep queue is intentionally empty, the user explicitly chose to promote `272`, and the repo already contains the exact truth gap named by the candidate: real phase-shaped baseline context and real tenant-review aggregate operation counts currently terminate in generic fallback labels.
- **Why this is the smallest viable implementation slice**: v1 stays on the existing shared progress contract, selected current run families, and one standards update. It explicitly avoids a workflow engine, dashboard work, provider health/support rollout, and child-run persistence.
- **Why close alternatives were deferred**:
- review-pack and evidence-snapshot progress remain owned by `specs/271-counted-progress-rollout/` because those families already have deterministic counted work units under current repo truth
- provider health and support diagnostics do not currently expose equivalent queued phase/composite progress truth through `OperationRun`
- `273 - Tenant Dashboard Active Operations Summary Card` remains a separate dashboard concern and should not be reopened here
## Related-Spec Guardrail Check
- `specs/270-operationrun-progress-contract/`: immediate prerequisite. This package extends the shared contract rather than replacing it.
- `specs/271-counted-progress-rollout/`: adjacent counted-rollout package. This package must not reopen stable-unit counted families or change the existing precedence between counted and non-counted modes.
- `specs/268-operationrun-activity-feedback/`: existing shell feedback owner. This package may refine the meaning of active progress labels but must not reopen the shell's terminal outcome, dismissal, or browser-surface contract.
- `specs/159-baseline-snapshot-truth/` and `specs/164-run-detail-hardening/`: inherited baseline truth only. This package may not blur artifact truth and execution truth or create a second run-detail semantics layer.
## Assumptions
- Spec 270 remains the authoritative shared progress contract and keeps current precedence intact.
- Current baseline capture and compare jobs already own the real lifecycle boundaries that phase v1 should describe; the slice only makes those boundaries operator-safe and canonical.
- Current evidence snapshot operations summary is available early enough in tenant-review queueing to seed a bounded composite summary without inventing a child-run graph.
## Risks
- Baseline phase labels can become noisy or technical if the implementation leaks raw internal step names instead of the bounded operator-safe vocabulary.
- `tenant.review.compose` may not always have enough early aggregate truth to justify a composite summary; that path must degrade safely instead of guessing.
- Touching both baseline jobs and tenant-review composition raises the risk of accidental scope widening into dashboard, report, or provider-specific progress work.
## Open Questions
- None blocking safe implementation. If a selected run family cannot produce stable operator-safe non-counted detail without speculative copy, that family must drop back to current generic fallback behavior rather than widening the slice.

View File

@ -0,0 +1,192 @@
---
description: "Task list for OperationRun Phase & Composite Progress v1"
---
# Tasks: OperationRun Phase & Composite Progress v1
**Input**: Design documents from `specs/272-operationrun-phase-composite-progress/`
**Prerequisites**: `specs/272-operationrun-phase-composite-progress/spec.md`, `specs/272-operationrun-phase-composite-progress/plan.md`, `specs/272-operationrun-phase-composite-progress/checklists/requirements.md`
**Review Artifact**: `specs/272-operationrun-phase-composite-progress/checklists/requirements.md` is the outcome-of-record for the review outcome class, workflow outcome, and test-governance outcome. If implementation widens into provider health or support-diagnostics rollout, review-pack overlap, dashboard work, child-run graph persistence, or a workflow engine, update that artifact before continuing.
**Tests**: REQUIRED (Pest). Keep proof bounded to existing Unit plus Feature suites for Ops-UX, Baselines, TenantReview, and current run-detail compatibility. Browser coverage remains owned by prior Ops-UX specs and must not become a hidden requirement here.
**Operations**: No new `OperationRun` type, no new lifecycle family, no fake percentages, no workflow engine, no new `summary_counts` keys, no dashboard card, no provider health or support-diagnostics rollout, and no child-run graph persistence.
**RBAC**: Reuse current `OperationRun` policies, baseline capabilities, and tenant-review capabilities. No tenantless leakage from tenant surfaces; non-counted progress must remain invisible for inaccessible runs.
**Shared Pattern Reuse**: Reuse `OperationRunProgressContract`, `OperationUxPresenter`, `OperationStatusNormalizer`, `OperationRunService`, and `docs/ui/tenantpilot-enterprise-ui-standards.md`. Do not create a second local progress translator in Blade, Livewire, or domain jobs.
**Filament / Panel Guardrails**: Filament remains v5 on Livewire v4. Provider registration remains unchanged in `apps/platform/bootstrap/providers.php`. No new panel, resource, global-search behavior, or asset strategy is allowed. This slice changes current progress truth only.
**Organization**: Tasks are grouped by user story so baseline capture, baseline compare, tenant-review composite progress, and the future-boundary documentation remain independently reviewable.
## Test Governance Notes
- Lane mix stays Unit plus Feature.
- Prefer extending current Ops-UX, Baselines, and TenantReview suites before creating a new family.
- Browser proof stays with prior Ops-UX specs and must not become a hidden requirement here.
- Validation commands must stay file-scoped and run through Sail.
## Phase 1: Setup (Shared Context)
**Purpose**: confirm the bounded manual-promotion slice, the inherited progress-contract rules, and the repo-real phase/composite seams before runtime edits begin.
- [x] T001 Review `specs/272-operationrun-phase-composite-progress/spec.md`, `specs/272-operationrun-phase-composite-progress/plan.md`, `specs/272-operationrun-phase-composite-progress/checklists/requirements.md`, `docs/product/spec-candidates.md`, `docs/product/roadmap.md`, `specs/270-operationrun-progress-contract/spec.md`, `specs/271-counted-progress-rollout/spec.md`, `specs/268-operationrun-activity-feedback/spec.md`, `docs/ui/tenantpilot-enterprise-ui-standards.md`, and `.specify/memory/constitution.md` together so the slice stays on repo-real non-counted progress and keeps counted overlap plus dashboard work explicitly out of scope.
- [x] T002 [P] Confirm the current shared contract and host seams in `apps/platform/app/Support/OpsUx/OperationRunProgressContract.php`, `apps/platform/app/Support/OpsUx/OperationUxPresenter.php`, `apps/platform/app/Support/OpsUx/OperationStatusNormalizer.php`, and the current shell progress host proofs in `apps/platform/tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php` and `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php`.
- [x] T003 [P] Confirm the current baseline phase-writer seams in `apps/platform/app/Services/Baselines/BaselineCaptureService.php`, `apps/platform/app/Jobs/CaptureBaselineSnapshotJob.php`, `apps/platform/app/Jobs/CompareBaselineToTenantJob.php`, and `apps/platform/app/Services/Baselines/BaselineContentCapturePhase.php`.
- [x] T004 [P] Confirm the current tenant-review composite seams and explicit exclusions in `apps/platform/app/Services/TenantReviews/TenantReviewService.php`, `apps/platform/app/Jobs/ComposeTenantReviewJob.php`, `apps/platform/app/Services/TenantReviews/TenantReviewSectionFactory.php`, and the related proof owner `apps/platform/tests/Feature/TenantReview/TenantReviewOperationsUxTest.php`.
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: settle the shared contract boundary, the selected non-counted vocabulary, and the focused proof owners before user-story runtime work begins.
**Critical**: no user-story runtime work should begin until this phase is complete.
- [x] T005 [P] Create or extend failing coverage in `apps/platform/tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php` for operator-safe phase/composite label derivation, malformed-metadata fallback, and the rule that current counted progress remains subordinate to truthful phase/composite precedence.
- [x] T006 [P] Extend `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php` and `apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php` only as needed so the shell proves selected phase/composite labels render without a progress bar and the full precedence chain `phased > composite > counted > activity` still holds, with counted mode available only when truthful `processed` and `total` exist.
- [x] T007 [P] Extend `apps/platform/tests/Feature/Filament/OperationRunBaselineTruthSurfaceTest.php` only if presenter or run-detail disclosure changes require proof that operator-safe phase/composite copy stays separate from terminal artifact truth and detailed diagnostics.
- [x] T008 [P] Review or extend `apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php`, `apps/platform/tests/Feature/TenantReview/TenantReviewRbacTest.php`, and `apps/platform/tests/Feature/Baselines/BaselineProfileAuthorizationTest.php` so current `404`/`403` semantics, inaccessible-run invisibility, and unchanged confirmation behavior remain explicit proof owners for this slice.
**Checkpoint**: the shared contract boundary and focused proof owners are settled before implementation begins.
---
## Phase 3: User Story 1 - See truthful phase progress for baseline capture (Priority: P1)
**Goal**: baseline capture exposes operator-safe phase truth for current lifecycle boundaries without inventing a percentage.
**Independent Test**: start baseline capture through the current service/job path, exercise current standard and full-content branches, and verify the active run exposes truthful phase labels while remaining explicitly non-counted.
### Tests for User Story 1
- [x] T009 [P] [US1] Extend `apps/platform/tests/Feature/Baselines/BaselineCaptureTest.php` and `apps/platform/tests/Feature/BaselineDriftEngine/CaptureBaselineContentTest.php` for phase metadata seeding at preflight or runtime-recheck, current subject or snapshot work, optional evidence capture, persistence, and finalization.
### Implementation for User Story 1
- [x] T010 [US1] Update `apps/platform/app/Services/Baselines/BaselineCaptureService.php` and `apps/platform/app/Jobs/CaptureBaselineSnapshotJob.php` only as needed so `baseline_capture` writes canonical phase key and operator-safe label details into existing `OperationRun.context` as work moves through current lifecycle boundaries.
- [x] T011 [US1] Review blocked, resumed, failed, and completed capture paths so terminal runs retain diagnostic context without leaving stale active-phase truth and so missing phase metadata degrades safely to the current generic fallback.
**Checkpoint**: User Story 1 is independently functional when baseline capture can describe its current phase truthfully without ever rendering fake counted progress.
---
## Phase 4: User Story 2 - See truthful phase progress for baseline compare (Priority: P1)
**Goal**: baseline compare exposes operator-safe phase truth for current preparation, evidence-capture, compare, persistence, and finalization boundaries without inventing a percentage.
**Independent Test**: queue a baseline compare that exercises current preparation and optional evidence-capture branches, then verify the run exposes truthful phase labels and remains explicitly non-counted.
### Tests for User Story 2
- [x] T012 [P] [US2] Extend `apps/platform/tests/Feature/Baselines/BaselineCompareResumeTokenTest.php` and `apps/platform/tests/Feature/Baselines/BaselineCompareExecutionGuardTest.php` only as needed for phase metadata around current preparation, evidence capture or resume, compare execution, persistence, and finalization.
- [x] T013 [P] [US2] Extend `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php` or `apps/platform/tests/Feature/Filament/OperationRunBaselineTruthSurfaceTest.php` only as needed so baseline compare phase labels remain operator-safe and explicitly non-counted on visible surfaces.
### Implementation for User Story 2
- [x] T014 [US2] Update `apps/platform/app/Jobs/CompareBaselineToTenantJob.php` and any immediately adjacent baseline helper only as needed so `baseline_compare` publishes canonical phase key and operator-safe label detail for current preparation, evidence capture or resume, compare execution, persistence, and finalization.
- [x] T015 [US2] Review partial, blocked, failed, and malformed-context compare paths so phase detail degrades safely when metadata is absent and never overrides terminal outcome or artifact-truth semantics.
**Checkpoint**: User Story 2 is independently functional when baseline compare can describe its current phase truthfully without falling back to fake counted progress.
---
## Phase 5: User Story 3 - See truthful composite progress for tenant review compose (Priority: P2)
**Goal**: `tenant.review.compose` exposes a bounded composite summary from current aggregate operation truth without pretending it is percentage progress.
**Independent Test**: create or refresh a tenant review from an evidence snapshot with known operations summary counts, then verify the active run exposes a truthful composite label with aggregate child hints while remaining non-counted.
### Tests for User Story 3
- [x] T016 [P] [US3] Extend `apps/platform/tests/Feature/TenantReview/TenantReviewOperationsUxTest.php` and current shell proof owners only as needed for composite summary labels derived from evidence-basis operation counts plus current failed or partial child hints, including safe fallback when aggregate truth is zero, missing, or too weak for composite disclosure.
### Implementation for User Story 3
- [x] T017 [US3] Update `apps/platform/app/Services/TenantReviews/TenantReviewService.php`, `apps/platform/app/Jobs/ComposeTenantReviewJob.php`, and `apps/platform/app/Services/TenantReviews/TenantReviewSectionFactory.php` only as needed so `tenant.review.compose` seeds bounded composite summary metadata from the current evidence snapshot operations summary before running work and finalizes it after composition completes, while degrading safely to generic activity/composite fallback when that aggregate truth is absent.
- [x] T018 [US3] Keep composite v1 bounded to `tenant.review.compose` and current aggregate summary fields; record provider health/support diagnostics, review-pack or evidence-snapshot overlap, and child-run link expansion as follow-up explicitly instead of widening the implementation slice.
**Checkpoint**: User Story 3 is independently functional when `tenant.review.compose` can summarize current aggregate operation posture truthfully without rendering counted progress.
---
## Phase 6: User Story 4 - Preserve non-counted progress boundaries in docs and review artifacts (Priority: P2)
**Goal**: future contributors can extend non-counted progress without silently reopening fake percentages, workflow-engine drift, or excluded run families.
**Independent Test**: review the standards update and the completed proof list together, then confirm that counted overlap, provider health/support diagnostics, dashboard work, and child-run graph persistence remain named follow-ups rather than hidden scope here.
### Implementation for User Story 4
- [x] T019 [US4] Update `docs/ui/tenantpilot-enterprise-ui-standards.md` with the current phase/composite rules: selected families only, operator-safe non-technical labels, counted precedence unchanged, composite summaries explicitly non-counted, and excluded candidate ideas named as later follow-ups.
- [x] T020 [US4] Review the resulting package and touched code to confirm there is still no workflow engine, no dashboard card, no provider health/support-diagnostics rollout, no review-pack or evidence-snapshot overlap, no child-run graph persistence, and no new `summary_counts` key.
**Checkpoint**: User Story 4 is independently functional when future-extension boundaries are explicit in both docs and the feature package.
---
## Phase 7: Polish & Cross-Cutting Validation
**Purpose**: validate the bounded slice, stop drift, and hand off a clean implementation path.
- [x] T021 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/OpsUx/OperationRunProgressContractTest.php`.
- [x] T022 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php tests/Feature/BaselineDriftEngine/CaptureBaselineContentTest.php tests/Feature/Baselines/BaselineCaptureTest.php tests/Feature/Baselines/BaselineCompareResumeTokenTest.php tests/Feature/Baselines/BaselineCompareExecutionGuardTest.php tests/Feature/TenantReview/TenantReviewOperationsUxTest.php tests/Feature/Filament/OperationRunBaselineTruthSurfaceTest.php tests/Feature/Operations/TenantlessOperationRunViewerTest.php tests/Feature/TenantReview/TenantReviewRbacTest.php tests/Feature/Baselines/BaselineProfileAuthorizationTest.php`.
- [x] T023 [P] Run `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` for touched platform files.
- [x] T024 [P] Review touched code to confirm Filament stays on Livewire v4, provider registration remains unchanged in `apps/platform/bootstrap/providers.php`, no new assets were registered, no new globally searchable resource behavior was introduced, no new parallel polling loop was added, and the full precedence chain `phased > composite > counted > activity` still holds with counted mode requiring truthful `processed` plus `total`.
---
## Dependencies & Execution Order
### Phase Dependencies
- **Phase 1 (Setup)**: no dependencies; start immediately.
- **Phase 2 (Foundational)**: depends on Phase 1 and blocks user-story work.
- **Phase 3 (US1)**: depends on Phase 2 and establishes the clearest repo-real phase rollout target.
- **Phase 4 (US2)**: depends on Phase 2 and should land after or alongside US1 so both baseline phase families share one operator-safe vocabulary.
- **Phase 5 (US3)**: depends on Phase 2 and should land after the baseline phase path so composite v1 reuses the same non-counted contract discipline.
- **Phase 6 (US4)**: depends on Phases 3 through 5 so the documented boundary matches the implemented slice.
- **Phase 7 (Polish)**: depends on all desired user stories being complete.
### User Story Dependencies
- **US1 (P1)**: independently testable after Phase 2 and delivers the clearest immediate value because baseline capture already persists repo-real phase truth.
- **US2 (P1)**: independently testable after Phase 2 and completes the baseline non-counted execution pair.
- **US3 (P2)**: independently testable after Phase 2 and completes the approved composite v1 slice for current aggregate tenant-review work.
- **US4 (P2)**: independently testable after Phases 3 through 5 and is required for package completion because the narrowed `271`/`272` boundary is part of the approved scope.
### Within Each User Story
- Write or extend the listed Pest coverage first and make it fail for the intended gap.
- Land the writer-side metadata changes before adjusting any shared host assertion that depends on that new truth.
- Re-run the narrowest affected validation command after each story checkpoint before moving on.
---
## Implementation Strategy
### Suggested MVP Scope
- MVP = **US1 + US2**, because the first enterprise-visible value arrives once the current baseline capture and compare families can show truthful non-counted phase progress through the shared contract.
### Incremental Delivery
1. Complete Phase 1 and Phase 2.
2. Deliver US1.
3. Deliver US2.
4. Deliver US3.
5. Land US4 documentation and boundary hardening.
6. Finish with focused validation and formatting.
### Team Strategy
1. Settle the shared contract boundary and proof owners first.
2. Keep baseline capture, baseline compare, and tenant-review composite edits serialized per family.
3. Do not widen into provider health/support progress, review-pack overlap, dashboard work, or a workflow engine while implementing this package.
---
## Deferred Follow-Ups / Non-Goals
- provider health or support-diagnostics progress rollout
- review-pack or evidence-snapshot non-counted progress overlap with Spec 271
- child-run graph persistence through `child_run_ids` or `operation_run_ids`
- `273 - Tenant Dashboard Active Operations Summary Card`
- any workflow-engine or AI-generated progress explanation layer