285 lines
24 KiB
Markdown
285 lines
24 KiB
Markdown
# Implementation Plan: Inventory Coverage Truth
|
|
|
|
**Branch**: `feat/177-inventory-coverage-truth` | **Date**: 2026-04-05 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/177-inventory-coverage-truth/spec.md`
|
|
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/177-inventory-coverage-truth/spec.md`
|
|
|
|
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
|
|
|
|
## Summary
|
|
|
|
Correct inventory coverage semantics by deriving tenant coverage truth from the latest completed inventory-sync run that contains usable per-type coverage payload, joining that run truth with current inventory-item counts and existing supported-type metadata, replacing the misleading coverage percentage in the inventory KPI header with operator-readable counts, refocusing the coverage page on tenant follow-up, and exposing a human-readable per-type coverage section on inventory-sync run detail. The implementation stays fully derived, introduces no new persistence, keeps capability and support metadata secondary, and preserves existing sync execution, authorization, and Ops-UX behavior.
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: PHP 8.4.15
|
|
**Primary Dependencies**: Laravel 12, Filament v5, Livewire v4, Pest v4, existing `InventoryItem`, `OperationRun`, `InventoryCoverage`, `InventoryPolicyTypeMeta`, `CoverageCapabilitiesResolver`, `InventoryKpiHeader`, `InventoryCoverage` page, and `OperationRunResource` enterprise-detail stack
|
|
**Storage**: PostgreSQL; existing `inventory_items` rows and `operation_runs.context` / `operation_runs.summary_counts` JSONB are reused with no schema change
|
|
**Testing**: Pest 4 unit and feature tests, including Filament or Livewire page coverage, run through Laravel Sail
|
|
**Target Platform**: Laravel monolith web application in Sail locally and containerized Linux deployment for staging and production
|
|
**Project Type**: web application
|
|
**Performance Goals**: DB-only render path for inventory summary and coverage surfaces; no render-time Graph calls; one tenant-scoped basis-run lookup plus grouped item counts per render; default-visible inventory surfaces expose covered versus follow-up types, basis-run context, and next-step guidance without raw JSON inspection
|
|
**Constraints**: Derived-only implementation; no new coverage table; no inventory-sync backend rewrite; no new Graph calls; no new destructive actions; capability metadata stays secondary; unauthorized run drill-through must degrade safely
|
|
**Scale/Scope**: One tenant at a time across three primary surfaces: inventory KPI header on the items list, the inventory coverage page, and canonical inventory-sync run detail; dozens of supported and foundation types rather than unbounded row counts
|
|
|
|
## Constitution Check
|
|
|
|
*GATE: Passed before Phase 0 research. Re-checked after Phase 1 design and still passing.*
|
|
|
|
| Principle | Pre-Research | Post-Design | Notes |
|
|
|-----------|--------------|-------------|-------|
|
|
| Inventory-first / snapshots-second | PASS | PASS | Coverage remains derived from current inventory observation and canonical inventory-sync runs; no snapshot or backup truth is introduced. |
|
|
| Read/write separation | PASS | PASS | The feature changes read-time surfaces only. Existing `Run Inventory Sync` semantics remain unchanged. |
|
|
| Graph contract path | PASS | PASS | No new Graph call path or contract registry entry is introduced. |
|
|
| Deterministic capabilities | PASS | PASS | Capability and support metadata remain derived from existing policy-type meta and capability resolvers. |
|
|
| Workspace + tenant isolation | PASS | PASS | All coverage truth stays tenant-scoped; canonical operations drill-through remains tenant-safe and entitlement-checked. |
|
|
| RBAC-UX authorization semantics | PASS | PASS | Non-members remain `404`; members lacking the run capability remain `403` on run detail; coverage surfaces must degrade safely instead of exposing broken links. |
|
|
| Run observability / Ops-UX | PASS | PASS | Existing `inventory_sync` `OperationRun` remains canonical. No new run type or new feedback surface is introduced. |
|
|
| Ops-UX lifecycle / summary counts | PASS | PASS | `OperationRunService` remains the only transition path; `summary_counts` remain canonical and numeric-only. The plan only adds read-time rendering. |
|
|
| Data minimization | PASS | PASS | Existing whitelisted inventory metadata and sanitized run context are reused; no new persisted payload surface is added. |
|
|
| Proportionality / no premature abstraction | PASS WITH JUSTIFIED DERIVED CONTRACT | PASS WITH JUSTIFIED DERIVED CONTRACT | One narrow runtime contract plus resolver is justified because current truth is split across run context, item counts, and supported-type metadata, and three operator surfaces already need the same synthesis. |
|
|
| Persisted truth / behavioral state | PASS | PASS | `Unknown` is derived presentation truth, not persisted domain state. No new tables or durable artifacts are introduced. |
|
|
| UI semantics / few layers | PASS | PASS | The design replaces a misleading KPI and capability-first report with one direct tenant-coverage read model instead of adding a wider semantic framework. |
|
|
| Badge semantics (BADGE-001) | PASS | PASS | Coverage state badges stay centralized and test-covered. No page-local badge language is introduced. |
|
|
| Filament-native UI / Action Surface Contract | PASS | PASS | Existing Filament tables, stats, sections, and enterprise-detail views are reused. The existing derived-row exception on `InventoryCoverage` remains the only UI exception. |
|
|
| Filament UX-001 | PASS | PASS | The coverage page remains a searchable, filterable table surface with one clear empty-state CTA; run detail remains a view-style operational page; no create or edit layout changes are needed. |
|
|
| List-surface review checklist reference | PASS | PASS | The inventory items list and inventory coverage page are governed by `docs/product/standards/list-surface-review-checklist.md`, which must be applied before sign-off. |
|
|
| Filament v5 / Livewire v4 compliance | PASS | PASS | The plan stays inside the existing Filament v5 + Livewire v4 stack. No legacy APIs are introduced. |
|
|
| Provider registration location | PASS | PASS | No panel or provider registration change is involved; Laravel 11+ provider registration remains in `bootstrap/providers.php`. |
|
|
| Global search hard rule | PASS | PASS | No globally searchable resource is added or modified. |
|
|
| Destructive action safety | PASS | PASS | No new destructive action is introduced. Existing sync start action remains non-destructive and capability-gated. |
|
|
| Asset strategy | PASS | PASS | No asset registration or `filament:assets` deployment change is required. |
|
|
| Testing truth (TEST-TRUTH-001) | PASS | PASS | The design adds focused resolver, surface, and RBAC-safe continuity tests that protect operator-visible business truth. |
|
|
|
|
## Phase 0 Research
|
|
|
|
Research outcomes are captured in `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/177-inventory-coverage-truth/research.md`.
|
|
|
|
Key decisions:
|
|
|
|
- Define the relevant coverage basis as the latest completed `inventory_sync` run for the tenant that contains a parseable `context.inventory.coverage` payload, regardless of overall run outcome, so `Failed` and `Skipped` remain visible when the run produced per-type truth.
|
|
- Keep tenant coverage fully derived from three existing sources: `OperationRun.context.inventory.coverage`, current `InventoryItem` counts, and existing supported-type metadata plus capability metadata.
|
|
- Introduce one narrow runtime contract and resolver in `App\Support\Inventory` rather than extending the low-level `InventoryCoverage` parser or adding request-scoped caching infrastructure.
|
|
- Replace the KPI percentage with count-based summary facts, since the spec explicitly prioritizes semantically clear counts over percentage language.
|
|
- Keep the coverage page as one truth-first report with summary + per-type table, and move capability metadata into secondary columns or reference treatment rather than preserving the current capability-first matrix.
|
|
- Add a dedicated human-readable per-type coverage section to the existing enterprise-detail run page instead of creating a new operations screen.
|
|
- Defer any first-class `stale` or freshness state family to later health hardening work; this slice surfaces the basis timestamp clearly without adding another semantic layer.
|
|
|
|
## Phase 1 Design
|
|
|
|
Design artifacts are created under `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/177-inventory-coverage-truth/`:
|
|
|
|
- `data-model.md`: existing persisted truths plus the new derived tenant coverage contract
|
|
- `contracts/inventory-coverage-truth.openapi.yaml`: logical surface contract for the inventory items summary, coverage page, and run detail continuity
|
|
- `contracts/tenant-coverage-truth.schema.json`: schema for the derived tenant coverage truth contract consumed by the UI
|
|
- `quickstart.md`: focused implementation and verification workflow
|
|
|
|
Design decisions:
|
|
|
|
- The new runtime contract is summary-first and derived; it does not become a new persisted truth source.
|
|
- `InventoryCoverage` remains the low-level parser for `OperationRun` context; a sibling resolver assembles tenant coverage truth by joining the parsed payload with item counts and metadata.
|
|
- The inventory KPI header will shift from `Coverage %` to count-based coverage signals and explicit basis-run context.
|
|
- The `InventoryCoverage` page will reuse its existing table surface but reorder the page around tenant coverage truth and follow-up, with capability metadata kept clearly secondary.
|
|
- Inventory-sync run detail will gain one enterprise-detail section backed by a custom Blade view under the existing `filament.infolists.entries.*` convention.
|
|
- No new request-scoped caching or cross-surface aggregate infrastructure is introduced in this slice; one resolver is sufficient.
|
|
|
|
## Project Structure
|
|
|
|
### Documentation (this feature)
|
|
|
|
```text
|
|
specs/177-inventory-coverage-truth/
|
|
├── spec.md
|
|
├── plan.md
|
|
├── research.md
|
|
├── data-model.md
|
|
├── quickstart.md
|
|
├── contracts/
|
|
│ ├── inventory-coverage-truth.openapi.yaml
|
|
│ └── tenant-coverage-truth.schema.json
|
|
├── checklists/
|
|
│ └── requirements.md
|
|
└── tasks.md
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
|
|
```text
|
|
app/
|
|
├── Filament/
|
|
│ ├── Pages/
|
|
│ │ └── InventoryCoverage.php
|
|
│ ├── Resources/
|
|
│ │ ├── InventoryItemResource/
|
|
│ │ │ └── Pages/
|
|
│ │ │ └── ListInventoryItems.php
|
|
│ │ └── OperationRunResource.php
|
|
│ ├── Pages/
|
|
│ │ └── Operations/
|
|
│ │ └── TenantlessOperationRunViewer.php
|
|
│ └── Widgets/
|
|
│ └── Inventory/
|
|
│ └── InventoryKpiHeader.php
|
|
├── Models/
|
|
│ ├── InventoryItem.php
|
|
│ └── OperationRun.php
|
|
└── Support/
|
|
└── Inventory/
|
|
├── CoverageCapabilitiesResolver.php
|
|
├── InventoryCoverage.php
|
|
├── InventoryPolicyTypeMeta.php
|
|
├── TenantCoverageTruth.php
|
|
└── TenantCoverageTruthResolver.php
|
|
|
|
resources/
|
|
└── views/
|
|
└── filament/
|
|
├── pages/
|
|
│ └── inventory-coverage.blade.php
|
|
└── infolists/
|
|
└── entries/
|
|
└── inventory-coverage-truth.blade.php
|
|
|
|
tests/
|
|
├── Feature/
|
|
│ ├── Filament/
|
|
│ │ ├── InventoryCoverageAdminTenantParityTest.php
|
|
│ │ ├── InventoryCoverageTableTest.php
|
|
│ │ ├── InventoryItemResourceTest.php
|
|
│ │ ├── InventoryPagesTest.php
|
|
│ │ └── OperationRunEnterpriseDetailPageTest.php
|
|
│ ├── Inventory/
|
|
│ │ ├── InventorySyncServiceTest.php
|
|
│ │ ├── InventorySyncStartSurfaceTest.php
|
|
│ │ └── RunInventorySyncJobTest.php
|
|
│ ├── Operations/
|
|
│ │ └── TenantlessOperationRunViewerTest.php
|
|
│ └── Rbac/
|
|
│ └── InventoryItemResourceAuthorizationTest.php
|
|
└── Unit/
|
|
└── Support/
|
|
└── Inventory/
|
|
└── TenantCoverageTruthResolverTest.php
|
|
```
|
|
|
|
**Structure Decision**: Keep the existing Laravel monolith layout. Add one narrow derived coverage contract plus resolver under `app/Support/Inventory`, update the three existing operator surfaces, add one custom enterprise-detail view, and extend the current feature and unit tests instead of creating new base directories or a broader presentation framework.
|
|
|
|
## Implementation Strategy
|
|
|
|
### Phase A — Introduce the Derived Coverage Contract
|
|
|
|
**Goal**: Add one explicit runtime contract that represents tenant coverage truth without changing persistence.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| A.1 | `app/Support/Inventory/TenantCoverageTruth.php` | Add a readonly runtime contract representing basis-run metadata, summary counts, and per-type tenant coverage rows |
|
|
| A.2 | `app/Support/Inventory/TenantCoverageTruthResolver.php` | Add the resolver that selects the latest completed coverage-bearing inventory-sync run, parses `InventoryCoverage`, joins current item counts, and synthesizes follow-up classification |
|
|
| A.3 | `app/Support/Inventory/InventoryCoverage.php` | Keep the low-level parser focused on run payload normalization; extend only if a small helper is needed for row access, not for tenant-level joining |
|
|
|
|
### Phase B — Refactor the Inventory KPI Header
|
|
|
|
**Goal**: Remove the misleading percentage and replace it with count-based tenant coverage truth.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| B.1 | `app/Filament/Widgets/Inventory/InventoryKpiHeader.php` | Replace `Coverage %` with count-based coverage stats such as succeeded types and types needing follow-up, plus explicit basis-run or no-sync context while keeping any restore or compare metadata clearly separate from coverage truth |
|
|
| B.2 | `tests/Feature/Filament/InventoryPagesTest.php` and `tests/Feature/Filament/InventoryItemResourceTest.php` | Preserve the existing inventory list inspect and sync-start affordances while asserting the summary surface no longer implies completeness from restorable-item share |
|
|
|
|
### Phase C — Recenter the Coverage Page Around Tenant Truth
|
|
|
|
**Goal**: Turn the current capability-first page into a tenant coverage report without removing support metadata entirely.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| C.1 | `app/Filament/Pages/InventoryCoverage.php` | Replace the current static capability row builder with rows sourced from `TenantCoverageTruthResolver`; lead with coverage-state, basis-run, observed-item, follow-up columns, and deterministic follow-up priority ordering |
|
|
| C.2 | `resources/views/filament/pages/inventory-coverage.blade.php` | Add or adjust the summary zone so the page cites the basis run, last sync time, explicit no-sync fallback, provider or permission follow-up guidance, and follow-up summary before the table |
|
|
| C.3 | `tests/Feature/Filament/InventoryCoverageTableTest.php` and `tests/Feature/Filament/InventoryCoverageAdminTenantParityTest.php` | Prove the page is tenant-coverage-first, keeps admin and tenant context parity, covers deterministic follow-up priority and no-basis-run messaging, and retains support, restore, and compare metadata only as secondary treatment |
|
|
|
|
### Phase D — Add Human-Readable Per-Type Results To Run Detail
|
|
|
|
**Goal**: Make inventory-sync per-type truth readable without raw JSON inspection.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| D.1 | `app/Filament/Resources/OperationRunResource.php` | For `inventory_sync` runs, add a dedicated enterprise-detail section that renders per-type coverage truth from `InventoryCoverage::fromContext()` |
|
|
| D.2 | `resources/views/filament/infolists/entries/inventory-coverage-truth.blade.php` | Add the custom section view used by the enterprise-detail builder for per-type status, item counts, follow-up priority, and provider or permission follow-up cues |
|
|
| D.3 | `tests/Feature/Operations/TenantlessOperationRunViewerTest.php` and `tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php` | Assert that inventory-sync runs render the new coverage section and that execution outcome stays distinct from per-type coverage truth |
|
|
|
|
### Phase E — Enforce RBAC-Safe Coverage Continuity
|
|
|
|
**Goal**: Ensure coverage surfaces can cite the backing run without leaking inaccessible operations.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| E.1 | `app/Support/Inventory/TenantCoverageTruthResolver.php` and calling surfaces | Include basis-run identity and safe continuity metadata without assuming the current user can always open the run |
|
|
| E.2 | `app/Filament/Widgets/Inventory/InventoryKpiHeader.php` and `app/Filament/Pages/InventoryCoverage.php` | Show direct links only when the current user can open the run; otherwise show explanatory guidance, explicit no-sync copy, and provider or permission follow-up guidance |
|
|
| E.3 | `tests/Feature/Rbac/InventoryItemResourceAuthorizationTest.php` and a new resolver or feature test for run continuity | Assert 404 or 403 behavior remains unchanged and the coverage UI degrades safely for users who cannot open the run |
|
|
|
|
### Phase F — Regression Protection and Verification
|
|
|
|
**Goal**: Lock the corrected semantics in place with focused tests and formatting.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| F.1 | `tests/Unit/Support/Inventory/TenantCoverageTruthResolverTest.php` | Cover basis-run selection, `Unknown` derivation, follow-up classification, and item-count joining |
|
|
| F.2 | Existing feature tests across Inventory, Filament, Operations, and RBAC | Cover KPI wording, truth-first coverage page behavior, run-detail readability, and safe continuity |
|
|
| F.3 | `vendor/bin/sail bin pint --dirty --format agent` and focused Pest runs | Apply formatting and run the smallest verification pack covering resolver logic, inventory surfaces, run detail, and RBAC continuity |
|
|
|
|
## Key Design Decisions
|
|
|
|
### D-001 — The basis run is the latest completed inventory-sync run with usable per-type coverage payload
|
|
|
|
This feature needs `Failed` and `Skipped` to remain visible when a run produced real per-type truth, so the basis selector must key off payload presence rather than optimistic run outcome alone.
|
|
|
|
### D-002 — `InventoryCoverage` stays a low-level parser; tenant synthesis lives in a sibling contract and resolver
|
|
|
|
`InventoryCoverage` already models the canonical `context.inventory.coverage` payload. Extending it to select runs, join item counts, and apply UI-facing follow-up logic would blur responsibilities and make the low-level parser less reusable.
|
|
|
|
### D-003 — Count-based KPI signals are the narrowest correction
|
|
|
|
The spec explicitly identifies the unqualified percentage as the misleading element. Replacing it with succeeded and follow-up type counts corrects the operator semantics without inventing a new score.
|
|
|
|
### D-004 — Capability metadata stays visible but subordinate
|
|
|
|
The product support matrix is still useful, but it must stop being the primary answer to a tenant coverage question. The plan keeps the metadata in secondary table columns or reference treatment instead of removing it completely.
|
|
|
|
### D-005 — No new stale state family in this slice
|
|
|
|
The spec allows stale semantics as optional. Adding them now would create another interpretation layer and broaden the scope beyond the immediate truth correction. This slice uses explicit timestamps and leaves broader freshness posture to later health work.
|
|
|
|
### D-006 — Follow-up priority is deterministic and severity-first
|
|
|
|
Coverage surfaces must not invent their own urgency rules. Follow-up ordering is `Failed` before `Unknown` before `Skipped`, then observed item count descending, then type label ascending, so the summary and the table can highlight the same first-review candidates without presentation drift.
|
|
|
|
## Risk Assessment
|
|
|
|
| Risk | Impact | Likelihood | Mitigation |
|
|
|------|--------|------------|------------|
|
|
| The resolver selects a run that should not be the coverage basis | High | Medium | Unit-test the basis selector across succeeded, partial, failed-with-payload, skipped-with-payload, and no-payload scenarios |
|
|
| Observed item counts are read as proof of coverage | High | Medium | Separate item counts from state columns and summary language, and add regression tests for types with items but `Unknown` coverage |
|
|
| Capability metadata still dominates the coverage page | Medium | Medium | Lead with coverage-state columns and summary facts, demote support metadata to secondary columns, and add page-level assertions |
|
|
| Run drill-through leaks inaccessible operations or creates dead links | High | Medium | Compute safe continuity metadata in the resolver or calling surface and test authorized, forbidden, and not-found paths |
|
|
| Run-detail coverage rendering conflicts with the existing enterprise-detail hierarchy | Medium | Low | Use one enterprise-detail view section under existing conventions and keep raw context JSON in the technical section |
|
|
|
|
## Test Strategy
|
|
|
|
- Add focused unit coverage for `TenantCoverageTruthResolver` so basis-run selection, `Unknown` derivation, and follow-up classification are verified without UI noise.
|
|
- Extend `InventoryCoverageTableTest` and `InventoryCoverageAdminTenantParityTest` to prove the page now answers tenant coverage truth first, applies deterministic follow-up priority, handles the no-basis-run case plainly, and keeps support, restore, and compare metadata secondary.
|
|
- Extend inventory list and page regressions so the KPI summary no longer exposes a misleading coverage percentage, plainly reports when no basis run exists, and preserves existing sync-start affordances plus canonical run links.
|
|
- Extend `TenantlessOperationRunViewerTest` and `OperationRunEnterpriseDetailPageTest` to verify inventory-sync runs render a readable per-type coverage section, provider or permission follow-up guidance, and keep execution outcome separate from coverage truth.
|
|
- Add RBAC coverage for safe continuity so users who can see inventory truth but cannot open the run receive non-clickable or explanatory guidance rather than broken drill-throughs.
|
|
- Run the smallest focused Sail test pack plus Pint before implementation completion.
|
|
|
|
## Complexity Tracking
|
|
|
|
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
|
|-----------|------------|-------------------------------------|
|
|
| New derived runtime contract plus resolver | Three existing surfaces need the same tenant coverage synthesis from run payload, item counts, and capability metadata | Recomputing the join inside each widget or page would duplicate business truth and make regression drift likely |
|
|
|
|
## Proportionality Review
|
|
|
|
- **Current operator problem**: Operators currently read a `Coverage` KPI that actually measures restorable-item share and a coverage page that primarily presents product support metadata, while the true per-type sync result lives in run context and is difficult to read.
|
|
- **Existing structure is insufficient because**: No existing object combines the relevant run basis, per-type run status, item counts, and support metadata into one tenant-coverage answer, and the current surfaces each infer their own incomplete version of coverage.
|
|
- **Narrowest correct implementation**: Add one derived runtime contract plus one resolver, then refit the existing KPI header, coverage page, and run-detail page around that contract without adding persistence or a broader framework.
|
|
- **Ownership cost created**: One new runtime contract, one resolver, one custom enterprise-detail view, and a focused set of unit and feature regressions.
|
|
- **Alternative intentionally rejected**: A persisted coverage table, a new percentage score, a request-scoped aggregate framework, or a broader inventory health layer were rejected because they exceed the scope of correcting already-available truth.
|
|
- **Release truth**: Current-release truth correction. |