TenantAtlas/specs/056-remove-legacy-bulkops/plan.md
2026-01-18 18:29:06 +01:00

7.2 KiB

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
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)

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)

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.