# Implementation Plan: OperationRun Progress Contract v1

**Branch**: `270-operationrun-progress-contract` | **Date**: 2026-05-04 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/270-operationrun-progress-contract/spec.md`

## Summary

This plan prepares one bounded Ops-UX foundation slice over existing `OperationRun` truth. The implementation path is to introduce one shared progress-semantics contract in the current `App\Support\OpsUx` family, move progress-mode decisions out of `bulk-operation-progress.blade.php`, and document the contract in `docs/ui/tenantpilot-enterprise-ui-standards.md`. The slice must stay on existing `OperationRun.status`, `OperationRun.outcome`, `summary_counts`, and `context` truth; it must not widen into counted writer rollout, dashboard redesign, terminal-notification changes, or new persistence.

Filament remains on Livewire v4, no panel-provider registration changes are required (`apps/platform/bootstrap/providers.php` remains authoritative), no globally searchable resource is added, and no asset registration or deployment step is expected.

## Inherited Baseline / Explicit Delta

### Inherited baseline

- `SummaryCountsNormalizer` and `OperationSummaryKeys` already sanitize and whitelist numeric `summary_counts` values.
- `OperationRunService` already owns `summary_counts` writes through `updateRun()`, `incrementSummaryCounts()`, and `maybeCompleteBulkRun()`.
- `ActiveRuns` already owns shell-visible run selection and terminal-success grace-window filtering.
- `BulkOperationProgress` and `bulk-operation-progress.blade.php` already render the current shell host, but the progress semantics are still decided inline in the Blade view.
- `specs/268-operationrun-activity-feedback/` already owns the shell terminal-success and terminal-follow-up slice.
- Historical Ops-UX specs already require numeric-only `summary_counts` and preserve the three-surface lifecycle contract.

### Explicit delta in this plan

- formalize one shared `OperationRun` progress capability and render-model contract
- centralize counted vs activity-only vs terminal no-progress semantics in one Ops-UX helper
- move current shell progress logic off inline Blade math and onto that shared contract
- document future-safe boundaries for `phased` and `composite` progress without rolling them out yet
- leave run-writer rollout, dashboard follow-up work, and phase/composite truth to later specs

## Technical Context

**Language/Version**: PHP 8.4, Laravel 12, Filament v5, Livewire v4  
**Primary Dependencies**: current Ops-UX support classes, native Filament widgets/Blade, Pest v4  
**Storage**: PostgreSQL via existing `operation_runs`; no new persistence  
**Testing**: Pest Unit + Feature coverage  
**Validation Lanes**: fast-feedback, confidence  
**Target Platform**: existing Laravel monolith in `apps/platform`, admin/operator plane only  
**Project Type**: Web application (Laravel monolith with Filament)  
**Performance Goals**: no new query families, no extra polling loops, and no slower-than-current shell rendering for active-run feedback  
**Constraints**: no new `summary_counts` keys, no new run lifecycle, no new persistence, and no browser-only proof requirement in this slice  
**Scale/Scope**: one shared contract, one shell adopter, one standards update, and focused regression coverage

## Likely Affected Repo Surfaces

- `apps/platform/app/Support/OpsUx/SummaryCountsNormalizer.php`
- `apps/platform/app/Support/OpsUx/OperationSummaryKeys.php`
- `apps/platform/app/Support/OpsUx/ActiveRuns.php`
- `apps/platform/app/Support/OpsUx/OperationStatusNormalizer.php`
- `apps/platform/app/Support/OpsUx/OperationUxPresenter.php`
- one new bounded helper under `apps/platform/app/Support/OpsUx/` for progress capability/render semantics
- `apps/platform/app/Livewire/BulkOperationProgress.php`
- `apps/platform/resources/views/livewire/bulk-operation-progress.blade.php`
- `apps/platform/app/Services/OperationRunService.php`
- `apps/platform/tests/Unit/Support/OpsUx/...`
- `apps/platform/tests/Feature/OpsUx/ActivityFeedbackSurfaceTest.php`
- `apps/platform/tests/Feature/OpsUx/BulkOperationProgressDbOnlyTest.php`
- `apps/platform/tests/Feature/OpsUx/SummaryCountsWhitelistTest.php`
- `docs/ui/tenantpilot-enterprise-ui-standards.md`

## UI / Filament & Livewire Fit

- Keep the changed surface Filament-native. The visible v1 adopter remains the existing Livewire shell host rather than a new page, widget family, or dashboard card.
- The shell host stays decision-first. The new contract decides only whether a progress line or bar is truthful; it does not widen the shell into a diagnostics surface.
- Monitoring collection/detail pages remain diagnostics-first drill-through targets. This slice prepares their future compatibility by naming one shared contract, not by redesigning their UI.
- The current shell host may keep bounded progress text or bars, but it must no longer calculate their eligibility or percentages inline.
- No new asset registration, panel configuration, or provider registration change is planned.

## RBAC / Policy Fit

- Existing `OperationRun` policies remain the first and only visibility gate.
- The progress contract derives output only after the current actor is already entitled to see the run.
- Tenant/admin plane behavior stays unchanged: no cross-plane expansion and no new authorization surface.
- No new mutation or retry action is introduced, so current confirmation/authorization behavior stays on existing start surfaces and run detail pages.

## Audit / Logging Fit

- Existing queued toasts and terminal DB notifications remain authoritative and unchanged.
- Existing run audit and Monitoring behavior remain the only audit trail. No new view-level or contract-level audit stream is introduced.
- `OperationRun.status` and `OperationRun.outcome` remain service-owned and unchanged.

## Data & Query Fit

- The contract derives only from current `OperationRun` truth: `status`, `outcome`, sanitized `summary_counts`, and bounded current `context` where trustworthy phase/composite truth may later exist.
- Determinate progress remains limited to current running work with trustworthy numeric `processed` and `total` counters.
- Outcome counters remain summary truth only; they are not reinterpreted as progress inputs.
- No migration, no new JSON schema, no backfill, and no cache layer are planned.

## UI / Surface Guardrail Plan

- **Guardrail scope**: changed surfaces
- **Native vs custom classification summary**: native Filament plus a bounded local Ops-UX helper refactor
- **Shared-family relevance**: Ops UX start feedback and execution-truth summaries
- **State layers in scope**: shell, page
- **Audience modes in scope**: operator-MSP
- **Decision/diagnostic/raw hierarchy plan**: decision-first on the shell host, diagnostics-first on Operations collection/detail
- **Raw/support gating plan**: raw/support evidence stays on the current diagnostics surfaces only
- **One-primary-action / duplicate-truth control**: the shell keeps the current dominant `View operation` action and only swaps local progress math for the shared contract; it does not add duplicate progress explanations
- **Handling modes by drift class or surface**: review-mandatory
- **Repository-signal treatment**: review-mandatory
- **Special surface test profiles**: global-context-shell
- **Required tests**: functional-core, state-contract
- **Exception path and spread control**: none planned; any attempt to add new writer semantics, a new browser family, or dashboard-specific progress logic resolves as `reject-or-split`
- **Active feature PR close-out entry**: Guardrail / Smoke Coverage

## Shared Pattern & System Fit

- **Cross-cutting feature marker**: yes
- **Systems touched**: current shell host, summary-count sanitization, progress disclosure semantics, and UI standards
- **Shared abstractions reused**: `SummaryCountsNormalizer`, `OperationSummaryKeys`, `ActiveRuns`, `OperationStatusNormalizer`, `OperationUxPresenter`, current Ops-UX shell host
- **New abstraction introduced? why?**: yes, one bounded shared progress contract/helper because the repo already has multiple real consumers and the current progress truth gap cannot stay view-local without drift
- **Why the existing abstraction was sufficient or insufficient**: the repo already owns lifecycle normalization and count sanitization, but it does not currently answer progress eligibility or progress mode once and centrally
- **Bounded deviation / spread control**: do not create multiple host-specific helpers, a registry, or a persisted progress model

## OperationRun UX Impact

- **Touches OperationRun start/completion/link UX?**: yes, for active-surface progress disclosure only
- **Central contract reused**: current Ops-UX start contract via `OperationRunLinks`, `OperationRunUrl`, `ActiveRuns`, `OperationStatusNormalizer`, and `OperationUxPresenter`
- **Delegated UX behaviors**: queued toast wording, canonical view/collection links, current browser-event dispatch, and existing terminal DB notifications remain delegated to the shared contract and unchanged
- **Surface-owned behavior kept local**: bounded shell layout and copy density only
- **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**: existing `OperationRun` truth, summary-count vocabulary, and operator-facing execution language only
- **Neutral platform terms / contracts preserved**: `Operation`, `activity`, `progress`, `terminal outcome`, `counted progress`
- **Retained provider-specific semantics and why**: none
- **Bounded extraction or follow-up path**: none

## Constitution Check

*GATE: Must pass before implementation begins and again before merge.*

- Inventory-first: PASS. The slice is fully derived from existing `OperationRun` truth.
- Read/write separation: PASS. No new write path or retry surface is introduced.
- Graph contract path: PASS. No Graph/provider interaction is added.
- Deterministic capabilities: PASS. Existing `OperationRun` policies remain authoritative.
- RBAC-UX: PASS. No plane expansion; tenant/admin visibility stays on current guards and deny-as-not-found semantics.
- Run observability: PASS. Existing start contract, terminal notifications, and Monitoring ownership remain unchanged while progress semantics are centralized.
- Ops-UX lifecycle: PASS. No change to service-owned status/outcome transitions or `summary_counts` ownership.
- Data minimization: PASS. Hosts stay compact and do not surface raw evidence by default.
- Test governance: PASS. Proof stays bounded to unit plus feature coverage.
- Proportionality / no premature abstraction: PASS. The helper is justified by multiple real consumers and avoids wider rollout or persistence.
- Persisted truth / behavioral state: PASS. No new table, cache, or progress-mode persistence.
- Shared pattern first / UI semantics / Filament-native UI: PASS. Existing helpers stay central, and the current shell moves closer to the Ops-UX contract.
- Provider boundary: PASS. No provider/platform seam changes.
- Filament/Laravel panel safety: PASS. Filament v5 stays on Livewire v4, provider registration remains in `apps/platform/bootstrap/providers.php`, and no new assets are planned.

**Gate evaluation**: PASS.

## Test Governance Check

- **Test purpose / classification by changed surface**: Unit for progress-capability/render-model truth; Feature for current shell adoption and shell-visible progress output
- **Affected validation lanes**: fast-feedback, confidence
- **Why this lane mix is the narrowest sufficient proof**: one unit suite proves the shared contract itself, while focused shell feature tests prove the visible adopter no longer calculates progress locally. Browser proof remains owned by `specs/268-operationrun-activity-feedback/` because this slice does not change layout or clickability.
- **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/OpsUx/SummaryCountsWhitelistTest.php`
- **Fixture / helper / factory / seed / context cost risks**: low to moderate; reuse current `OperationRun` factories and tenant helpers instead of introducing new provider-heavy defaults
- **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**: reviewers should rerun the focused commands above, then confirm the shell uses one shared progress contract, queued runs stay indeterminate, terminal runs stay terminal, and outcome counters never create a percentage
- **Budget / baseline / trend follow-up**: none expected beyond a small feature-local increase
- **Review-stop questions**: did the helper stay bounded, did the shell lose its inline progress math, did any new `summary_counts` keys or writer semantics appear, and were `269`, `271`, `272`, and `273` kept out of scope?
- **Escalation path**: `reject-or-split` for any writer rollout, dashboard redesign, or persisted progress model
- **Active feature PR close-out entry**: Guardrail / Smoke Coverage
- **Why no dedicated follow-up spec is needed**: this package is itself the bounded contract layer. Later counted rollout and phase/composite rollout remain explicit follow-up specs rather than hidden growth here.

## Project Structure

### Documentation (this feature)

```text
specs/270-operationrun-progress-contract/
├── spec.md
├── plan.md
├── tasks.md
└── checklists/
    └── requirements.md
```

This preparation package intentionally stays on the core artifacts plus the readiness checklist. The repo already contains the relevant Ops-UX truth, current shell host, and adjacent tests, so no extra research, data-model, or contract package is required for a bounded implementation handoff.

### Source Code (expected implementation surfaces)

```text
apps/platform/app/Support/OpsUx/
apps/platform/app/Livewire/BulkOperationProgress.php
apps/platform/resources/views/livewire/bulk-operation-progress.blade.php
apps/platform/app/Services/OperationRunService.php
apps/platform/tests/Unit/Support/OpsUx/
apps/platform/tests/Feature/OpsUx/
docs/ui/tenantpilot-enterprise-ui-standards.md
```

**Structure Decision**: keep the implementation local to the existing Ops-UX support family and the current shell host. Do not introduce a new activity or progress framework outside `App\Support\OpsUx`.

## Data / Migration Implications

- No migration or new table is planned.
- No new persisted user preference or progress-mode storage is allowed.
- No new cache layer, backfill, or asset/deploy step should be required for v1.

## Rollout Considerations

- Filament remains v5 on Livewire v4. No panel-provider change is required, and provider registration remains in `apps/platform/bootstrap/providers.php`.
- No global search change is required because the slice changes shared progress semantics, not resource discovery.
- No destructive action is added. Existing start/retry/detail surfaces remain the only mutation owners.
- No new asset registration is expected.

## Risk Controls

- Reject any implementation that reopens the shell terminal-outcome slice already owned by `specs/268-operationrun-activity-feedback/` and the deferred `269` candidate.
- Reject any implementation that introduces new `summary_counts` keys, a persisted progress mode, or a new `OperationRun` lifecycle.
- Reject any implementation that derives percentages from status, duration, stale heuristics, or outcome counters.
- Reject any implementation that widens the slice into dashboard-specific redesign, activity tray work, or counted writer rollout.
- Reject any implementation that leaves a second progress calculator in Blade, Livewire, or another current host surface.

## Implementation Phases

### Phase 0 - Confirm Current Progress Truth And Drift Seams

- Verify the current writer seams (`OperationRunService`, `SummaryCountsNormalizer`, `OperationSummaryKeys`) and the current visible adopter (`BulkOperationProgress`).

### Phase 1 - Encode The Shared Progress Contract

- Introduce one shared progress contract/helper that classifies capability and derives render-safe output from existing `OperationRun` truth.

### Phase 2 - Adopt The Current Shell Host

- Move shell progress eligibility and percentage math out of `bulk-operation-progress.blade.php` and onto the shared contract.

### Phase 3 - Record The Guardrail And Future Boundaries

- Update the UI standards and the focused tests so later specs inherit the same contract instead of re-explaining it locally.

## Proportionality Review

- **Current operator problem**: the repo has truthful counters and lifecycle state, but the current visible host still computes progress ad hoc.
- **Existing structure is insufficient because**: sanitization and lifecycle normalization alone do not decide whether determinate progress is allowed.
- **Narrowest correct implementation**: one shared progress-semantics helper plus shell adoption and one standards-doc update.
- **Ownership cost created**: one helper, focused tests, and one standards update.
- **Alternative intentionally rejected**: view-local math or persisted progress modes were rejected because they either preserve drift or add unjustified persistence.
- **Release truth**: current-release truth. The repo already renders progress and already stores the counts needed to centralize the semantics now.