TenantAtlas/specs/046-inventory-sync-button/plan.md
ahmido cf5b0027e3 046-inventory-sync-button (#47)
Zusammenfassung: Fügt im „Run Inventory Sync“-Modal einen include_dependencies-Toggle hinzu und persistiert die Auswahl in der InventorySyncRun.selection_payload. Tests, Quickstart und Tasks wurden entsprechend aktualisiert.

Files: InventoryLanding.php, InventorySyncButtonTest.php, quickstart.md, tasks.md
Motivation: Ermöglicht explizites Ein-/Ausschalten der Dependency-Extraktion pro Sync-Run (z. B. Assignments/Scope Tags/Foundations), statt starrer Defaults. Passt zur bestehenden selection_hash-Logik (InventorySelectionHasher) und zur deterministischen Selektionspersistenz.
Verhalten: include_dependencies ist im Modal standardmäßig true. Wird die Option gesetzt, landet der Wert als bool im selection_payload und beeinflusst selection_hash über die Normalisierung.
Tests: Neuer/angepasster Pest-Test stellt sicher, dass include_dependencies in selection_payload persistiert. Lokaler Testlauf:
./vendor/bin/sail artisan test tests/Feature/Inventory/InventorySyncButtonTest.php → alle Tests für diese Datei bestanden.
./vendor/bin/pint --dirty wurde ausgeführt (Formatting ok).
How to test (quick):
Start Sail + Queue:
Im Admin → Inventory: „Run Inventory Sync“ öffnen, Include dependencies umschalten, ausführen.
Prüfen: neu erstellter InventorySyncRun.selection_payload.include_dependencies ist der gesetzten Auswahl entsprechend. Oder laufen lassen:
Notes / Next steps:
Diese Änderung bereitet den Weg, später die Dependency-Extraction (042-inventory-dependencies-graph) optional tiefer zu integrieren.
Working tree ist sauber; es gibt ein nicht eingebundenes Verzeichnis 0800-future-features (unrelated).

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local>
Reviewed-on: #47
2026-01-09 22:15:04 +00:00

141 lines
6.3 KiB
Markdown

# 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 `InventorySyncRun` and `BulkOperationRun`.
- Data minimization: Inventory sync continues to store metadata + sanitized meta JSON; no secrets in notifications/logs.
## Project Structure
### Documentation (this feature)
```text
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)
```text
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.md`
- `specs/046-inventory-sync-button/contracts/internal-actions.md`
## Phase 2 Plan (Implementation Outline)
### 1) Data model: store initiator on run record
- Add nullable `user_id` foreign key to `inventory_sync_runs`.
- Add `InventorySyncRun::user()` relationship.
- Backfill is not required (existing rows can remain `null`).
### 2) Job orchestration: queued execution + progress tracking
- Add `RunInventorySyncJob` that:
- Loads the tenant + user + `BulkOperationRun`.
- Marks the `BulkOperationRun` as running (`BulkOperationService::start`).
- Runs inventory sync via `InventorySyncService` in a way that sets `InventorySyncRun.user_id`.
- Records bulk progress as a single-item operation:
- success: `recordSuccess` + `complete`
- failure/skip: `recordFailure`/`recordSkippedWithReason` + `complete` (or `fail` for hard failures)
- Sends Filament database notifications to the initiating user on completion/failure.
### 3) UI: Inventory “Run Inventory Sync” action
- Add a header action on `InventoryLanding`:
- Visible/authorized only if `auth()->user()` is a `User` and `canSyncTenant(Tenant::current())`.
- On click:
- Compute default selection payload.
- Pre-flight check: if a matching `InventorySyncRun` is currently `running` (same tenant + selection hash), show an informational notification and do not dispatch.
- Create a `BulkOperationRun` via `BulkOperationService::createRun(...)` with `total_items=1`.
- Send a started Filament DB notification with the check bottom right progress bar copy.
- Dispatch `RunInventorySyncJob` with identifiers.
### 4) Audit trail
- Use `AuditLogger` to 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`.
- `RunInventorySyncJob` sets `InventorySyncRun.user_id`.
- unauthorized user cannot see/execute the action.
- concurrency case: when a run is already `running`, a second dispatch is prevented.