- Use searchable multi-select with select all/clear\n- Track bulk progress per policy type\n- Update tests and spec tasks/quickstart
6.3 KiB
Implementation Plan: Inventory Sync Button (046)
Branch: 046-inventory-sync-button | Date: 2026-01-08 | Spec: specs/046-inventory-sync-button/spec.md
Input: Feature specification from specs/046-inventory-sync-button/spec.md
Summary
Add a “Run Inventory Sync” action in the Inventory area that dispatches a queued job, records a new InventorySyncRun attributed to the initiating user, and provides visibility via:
- Filament database notifications
- Existing bottom-right progress widget (powered by
BulkOperationRun+BulkOperationProgress)
Technical Context
Language/Version: PHP 8.4.15
Primary Dependencies: Laravel 12, Filament v4, Livewire v3
Storage: PostgreSQL (JSONB used for run payload + error context)
Testing: Pest v4 (Laravel test runner)
Target Platform: Containerized (Laravel Sail locally), deployed via Dokploy
Project Type: Web application (Laravel + Filament admin panel)
Performance Goals: UI action should enqueue quickly (<2s perceived) and never block on Graph calls
Constraints: Tenant-isolated, idempotent start semantics, observable background execution, queue workers may be down
Scale/Scope: Single-tenant interactive action; one run per tenant/selection at a time (locks/concurrency)
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
- Inventory-first: This feature refreshes Inventory (“last observed”) and does not touch snapshots/backups.
- Read/write separation: No Graph writes. Only reads + local DB writes (run records + inventory items).
- Graph contract path: Inventory sync already uses
GraphClientInterface; this feature does not add new Graph endpoints. - Deterministic capabilities: Uses
PolicyTypeResolver+config('tenantpilot.supported_policy_types')to derive default selection. - Tenant isolation: Uses
Tenant::current()for all run creation and progress tracking. - Automation: Uses existing inventory locks/concurrency; runs are observable via
InventorySyncRunandBulkOperationRun. - Data minimization: Inventory sync continues to store metadata + sanitized meta JSON; no secrets in notifications/logs.
Project Structure
Documentation (this feature)
specs/046-inventory-sync-button/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ └── internal-actions.md
└── tasks.md # Phase 2 output (/speckit.tasks) - not created here
Source Code (repository root)
app/
├── Filament/
│ └── Pages/
│ └── InventoryLanding.php # add header action “Run Inventory Sync”
├── Jobs/
│ └── RunInventorySyncJob.php # new queued job
├── Models/
│ ├── InventorySyncRun.php # add user relationship (nullable)
│ └── BulkOperationRun.php # reused by progress widget
├── Services/
│ ├── BulkOperationService.php # reused for progress + audit
│ ├── Intune/
│ │ └── AuditLogger.php # reused for inventory audit trail
│ └── Inventory/
│ └── InventorySyncService.php # add entrypoint to run sync attributed to user
database/migrations/
└── xxxx_xx_xx_xxxxxx_add_user_id_to_inventory_sync_runs_table.php
resources/views/
└── filament/pages/inventory-landing.blade.php # may need to show action if not already in header
tests/Feature/
└── Inventory/InventorySyncButtonTest.php # new Pest feature test
Structure Decision: Web application, implemented in Filament page actions + Laravel queued job.
Phase 0 Output (Research)
Completed in specs/046-inventory-sync-button/research.md.
Key decisions used in this plan:
- Reuse existing bottom-right progress widget by creating a
BulkOperationRun(resource=inventory,action=sync). - Authorize using existing tenant role capability:
User::canSyncTenant(Tenant::current()). - Default selection = “full inventory” (all supported policy types, foundations + dependencies enabled).
Phase 1 Output (Design)
Completed in:
specs/046-inventory-sync-button/data-model.mdspecs/046-inventory-sync-button/contracts/internal-actions.md
Phase 2 Plan (Implementation Outline)
1) Data model: store initiator on run record
- Add nullable
user_idforeign key toinventory_sync_runs. - Add
InventorySyncRun::user()relationship. - Backfill is not required (existing rows can remain
null).
2) Job orchestration: queued execution + progress tracking
- Add
RunInventorySyncJobthat:- Loads the tenant + user +
BulkOperationRun. - Marks the
BulkOperationRunas running (BulkOperationService::start). - Runs inventory sync via
InventorySyncServicein a way that setsInventorySyncRun.user_id. - Records bulk progress as a single-item operation:
- success:
recordSuccess+complete - failure/skip:
recordFailure/recordSkippedWithReason+complete(orfailfor hard failures)
- success:
- Sends Filament database notifications to the initiating user on completion/failure.
- Loads the tenant + user +
3) UI: Inventory “Run Inventory Sync” action
- Add a header action on
InventoryLanding:- Visible/authorized only if
auth()->user()is aUserandcanSyncTenant(Tenant::current()). - On click:
- Compute default selection payload.
- Pre-flight check: if a matching
InventorySyncRunis currentlyrunning(same tenant + selection hash), show an informational notification and do not dispatch. - Create a
BulkOperationRunviaBulkOperationService::createRun(...)withtotal_items=1. - Send a “started” Filament DB notification with the “check bottom right progress bar” copy.
- Dispatch
RunInventorySyncJobwith identifiers.
- Visible/authorized only if
4) Audit trail
- Use
AuditLoggerto emit at minimum:inventory.sync.dispatched- terminal event keyed by outcome:
inventory.sync.completed|inventory.sync.failed|inventory.sync.skipped
5) Tests (Pest)
- New feature test covering:
- authorized user can dispatch a sync from the Inventory page and it creates a
BulkOperationRun. RunInventorySyncJobsetsInventorySyncRun.user_id.- unauthorized user cannot see/execute the action.
- concurrency case: when a run is already
running, a second dispatch is prevented.
- authorized user can dispatch a sync from the Inventory page and it creates a