# Implementation Plan: Remove Legacy BulkOperationRun & Canonicalize Operations (v1.0)

**Branch**: `056-remove-legacy-bulkops` | **Date**: 2026-01-18 | **Spec**: [specs/056-remove-legacy-bulkops/spec.md](./spec.md)  
**Input**: Feature specification from `/specs/056-remove-legacy-bulkops/spec.md`

**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.

## Summary

Unify all bulk/operational work onto `OperationRun` (canonical run model + Monitoring surface) by migrating all legacy `BulkOperationRun` workflows to OperationRun-backed orchestration, removing the legacy stack (model/service/table/UI), and adding guardrails that prevent reintroduction.

## Technical Context

**Language/Version**: PHP 8.4.x  
**Primary Dependencies**: Laravel 12, Filament v4, Livewire v3  
**Storage**: PostgreSQL (JSONB in `operation_runs.context`, `operation_runs.summary_counts`)  
**Testing**: Pest (PHPUnit 12)  
**Target Platform**: Docker via Laravel Sail (local); Dokploy (staging/prod)
**Project Type**: Web application (Laravel monolith)  
**Performance Goals**: Calm polling UX for Monitoring; bulk orchestration chunked and resilient to throttling; per-scope concurrency default=1  
**Constraints**: Tenant isolation; no secrets in run failures/notifications; no remote work during UI render; Monitoring is DB-only  
**Scale/Scope**: Bulk actions may target large selections; orchestration must remain idempotent and debounced by run identity

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

- Inventory-first: N/A (this feature is operations plumbing; no inventory semantics change)
- Read/write separation: PASS (bulk actions remain enqueue-only; write paths are job-backed and auditable)
- Graph contract path: PASS (no new render-side Graph calls; any remote work stays behind queue + existing Graph client boundary)
- Deterministic capabilities: PASS (no capability derivation changes)
- Tenant isolation: PASS (OperationRun is tenant-scoped; bulk dedupe is tenant-wide; selection identity is deterministic)
- Run observability: PASS (bulk is always OperationRun-backed; DB-only <2s actions remain audit-only)
- Automation: PASS (locks + idempotency required; per-target concurrency is config-driven default=1)
- Data minimization: PASS (failure summaries stay sanitized; no secrets in run records)

## Project Structure

### Documentation (this feature)

```text
specs/056-remove-legacy-bulkops/
├── plan.md              # This file (/speckit.plan command output)
├── research.md          # Phase 0 output (/speckit.plan command)
├── data-model.md        # Phase 1 output (/speckit.plan command)
├── quickstart.md        # Phase 1 output (/speckit.plan command)
├── contracts/           # Phase 1 output (/speckit.plan command)
└── tasks.md             # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
```

### Source Code (repository root)

```text
app/
├── Filament/
│   ├── Resources/
│   └── Pages/
├── Jobs/
├── Models/
├── Notifications/
├── Services/
└── Support/

config/
├── tenantpilot.php
└── graph_contracts.php

database/
├── migrations/
└── factories/

resources/
└── views/

routes/
└── web.php

tests/
├── Feature/
└── Unit/
```

**Structure Decision**: Laravel web application (monolith). Feature changes are expected primarily under `app/` (runs, jobs, Filament actions), `database/migrations/` (dropping legacy tables), and `tests/` (Pest guardrails).

## Complexity Tracking

> **Fill ONLY if Constitution Check has violations that must be justified**

| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| None | N/A | N/A |

## Constitution Check (Post-Design)

*Re-check after Phase 1 outputs are complete.*

- Monitoring remains DB-only at render time.
- Start surfaces remain enqueue-only.
- Bulk work is always OperationRun-backed.
- Per-target scope concurrency is config-driven (default=1).
- Bulk idempotency uses hybrid selection identity.

## Phase 0 — Outline & Research (output: research.md)

### Discovery signals (repo sweep)

Legacy bulk-run usage exists and must be migrated/removed:

- Jobs using legacy run/service include:
  - `app/Jobs/BulkPolicySyncJob.php`
  - `app/Jobs/BulkPolicyDeleteJob.php`
  - `app/Jobs/BulkBackupSetDeleteJob.php`
  - `app/Jobs/BulkPolicyVersionPruneJob.php`
  - `app/Jobs/BulkPolicyVersionForceDeleteJob.php`
  - `app/Jobs/BulkRestoreRunDeleteJob.php`
  - `app/Jobs/BulkTenantSyncJob.php`
  - `app/Jobs/CapturePolicySnapshotJob.php`
  - `app/Jobs/GenerateDriftFindingsJob.php` (mixed: legacy + OperationRun)

Legacy data layer present:

- Model: `app/Models/BulkOperationRun.php`
- Service: `app/Services/BulkOperationService.php`
- Migrations: `database/migrations/*_create_bulk_operation_runs_table.php`, `*_add_idempotency_key_to_bulk_operation_runs_table.php`, `*_increase_bulk_operation_runs_status_length.php`
- Factory/Seeder: `database/factories/BulkOperationRunFactory.php`, `database/seeders/BulkOperationsTestSeeder.php`

Existing canonical run patterns to reuse:

- `app/Services/OperationRunService.php` provides tenant-wide active-run dedupe and safe dispatch semantics.
- `app/Support/OperationCatalog.php` centralizes labels/durations and allowed summary keys.
- `app/Support/OpsUx/OperationSummaryKeys.php` + `SummaryCountsNormalizer` enforce summary_counts contract.

Concurrency/idempotency patterns already exist and should be adapted:

- `app/Services/Inventory/InventoryConcurrencyLimiter.php` uses per-scope cache locks with config-driven defaults.
- `app/Services/Inventory/InventorySyncService.php` uses selection hashing + selection locks to prevent duplicate work.

### Research outputs (decisions)

- Per-target scope concurrency is configuration-driven with default=1.
- Bulk selection identity is hybrid (IDs-hash when explicit IDs; query-hash when “select all via filter/query”).
- Legacy bulk-run history is not imported into OperationRun (default path).

## Phase 1 — Design & Contracts (outputs: data-model.md, contracts/*, quickstart.md)

### Design topics

- Define the canonical bulk orchestration shape around OperationRun (orchestrator + workers) while preserving the constitution feedback surfaces.
- Define the minimal run context contract for directory-targeted runs (target scope fields + idempotency fingerprint fields).
- Extend operation type catalog for newly migrated bulk operations.

### Contract artifacts

- JSON schema for `operation_runs.context` for bulk operations (target scope + selection identity + idempotency).
- OpenAPI sketch for internal operation-triggering endpoints (if applicable) as a stable contract for “enqueue-only” start surfaces.

## Phase 2 — Planning (implementation outline; detailed tasks live in tasks.md)

- Perform required discovery sweep and create a migration report.
- Migrate each legacy bulk workflow to OperationRun-backed orchestration.
- Remove legacy model/service/table/UI surfaces.
- Add hard guardrails (CI/test) to forbid legacy references and verify run-backed behavior.
- Add targeted Pest tests for bulk actions, per-scope throttling default=1, and summary key normalization.
