164 lines
7.2 KiB
Markdown
164 lines
7.2 KiB
Markdown
# 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.
|