Adds Inventory Sync toggle include_foundations (default true) + persistence tests Adds Coverage “Dependencies” column (✅/—) derived deterministically from graph_contracts (no Graph calls) Spec/tasks/checklists updated + tasks ticked off Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local> Reviewed-on: #51
165 lines
7.5 KiB
Markdown
165 lines
7.5 KiB
Markdown
# Feature Specification: Foundations in Inventory (047)
|
||
|
||
**Feature Branch**: `feat/047-inventory-foundations-nodes`
|
||
**Created**: 2026-01-10
|
||
**Status**: Draft
|
||
|
||
## Purpose
|
||
|
||
Make foundational Intune objects (Scope Tags, Assignment Filters, Notification Templates) first-class Inventory nodes so:
|
||
- Dependency name resolution (Spec 042.2) can resolve display names locally
|
||
- Inventory coverage can communicate both **Policies** and **Foundations**
|
||
- Sync behavior matches selection flags (`include_foundations=true`)
|
||
|
||
## Clarifications
|
||
|
||
### Session 2026-01-10
|
||
|
||
- Q: How should NFR-002 (Data minimization) be defined/tested? → A: Define it as `meta_jsonb == InventoryMetaSanitizer::sanitize(meta_jsonb)` and `json_encode(meta_jsonb)` must not contain `Bearer `.
|
||
- Q: Should “no UI Graph calls” be enforced by an automated test guard? → A: Yes — add a test that fails if any UI rendering/resolution path calls `GraphClientInterface`.
|
||
- Q: What should happen if a foundation object is not returned by Graph in a later run? → A: Do not delete InventoryItem rows; treat Inventory as “last observed” and let entries become stale (no `last_seen_*` update).
|
||
- Q: Should `include_foundations=false` hide/purge foundations in the UI? → A: No — it only controls what this run syncs and counts; foundations remain visible if they exist and may become stale via `last_seen_*`.
|
||
- Q: How should unresolved dependency targets be displayed (without Graph calls)? → A: Show `Unresolved (<id>)` so missing foundations are visible for debugging.
|
||
|
||
## Scope
|
||
|
||
### In scope (MVP)
|
||
- Sync foundational object types as InventoryItems when `include_foundations=true`
|
||
- Show foundations in Inventory UI (items list + coverage)
|
||
- Enable local resolution in 042.2 (no additional Graph calls from the UI)
|
||
|
||
### Out of scope
|
||
- Entra group inventory / group name resolution
|
||
- Additional foundation types beyond the initial list (can be extended later)
|
||
- Any Intune write paths (create/update/delete)
|
||
|
||
## Users
|
||
|
||
- Tenant Admin (primary)
|
||
- MSP Operator (read-only cross-tenant later; not required here)
|
||
|
||
## Terminology
|
||
|
||
- **Policy Nodes**: InventoryItems whose `policy_type` is in `tenantpilot.supported_policy_types`
|
||
- **Foundation Nodes**: InventoryItems whose `policy_type` is in `tenantpilot.foundation_types`
|
||
- **Edges**: Dependency relationships stored in `inventory_links` (Spec 042)
|
||
|
||
## User Scenarios & Testing
|
||
|
||
### Scenario 1: Sync policies + foundations
|
||
Given I start an inventory sync with `include_foundations=true`
|
||
When the sync completes successfully
|
||
Then foundation nodes (scope tags, assignment filters, templates) exist as InventoryItems for the tenant.
|
||
|
||
### Scenario 2: Resolve dependency names
|
||
Given an InventoryItem has dependencies referencing scope tags/assignment filters
|
||
When I view the item’s dependencies
|
||
Then the UI shows the resolved display names (local DB) instead of unresolved targets.
|
||
If a dependency target cannot be resolved locally, the UI MUST display `Unresolved (<id>)` (no Graph calls).
|
||
|
||
### Scenario 3: Inventory browsing
|
||
Given Inventory Items contain both policies and foundations
|
||
When I filter inventory to “Foundations”
|
||
Then I only see foundation nodes (and can search by name).
|
||
|
||
### Scenario 4: Coverage communication
|
||
When I open Inventory Coverage
|
||
Then I can view both “Policies” and “Foundations” support matrices.
|
||
|
||
## Functional Requirements
|
||
|
||
### FR-001 Foundations types (MVP)
|
||
System MUST support syncing the following foundation policy_types as InventoryItems:
|
||
- `roleScopeTag` (Scope Tags)
|
||
- `assignmentFilter` (Assignment Filters)
|
||
- `notificationMessageTemplate` (Notification Message Templates)
|
||
|
||
Source of truth: `config('tenantpilot.foundation_types')`.
|
||
|
||
### FR-002 Selection behavior
|
||
If `include_foundations=true`, an inventory sync run MUST:
|
||
- sync selected policy types
|
||
- AND sync all foundation types from `tenantpilot.foundation_types` for the tenant
|
||
|
||
If `include_foundations=false`, foundation types MUST NOT be synced as inventory items.
|
||
|
||
`include_foundations` only controls what the run observes/upserts (and therefore run counts); it MUST NOT purge existing foundation InventoryItems or “magically” hide them in the UI.
|
||
|
||
### FR-003 InventoryItems shape
|
||
Foundation nodes MUST be stored as InventoryItems using the existing schema:
|
||
- `tenant_id`, `policy_type`, `external_id`, `display_name`, `category`, `platform`, `meta_jsonb`, `last_seen_at`, `last_seen_run_id`
|
||
|
||
Foundation nodes MUST be stored as InventoryItems and MUST have:
|
||
- `policy_type` set to the foundation type key (e.g. `roleScopeTag`, `assignmentFilter`, `notificationMessageTemplate`)
|
||
- `category` set to the literal string `Foundations` (used for UI filtering/presets)
|
||
|
||
### FR-004 Inventory Coverage UI
|
||
Coverage page MUST present:
|
||
- “Policies” table (existing behavior)
|
||
- “Foundations” table (new; derived from `tenantpilot.foundation_types`)
|
||
|
||
#### FR-COV-DEP-001 Dependencies column
|
||
Coverage MUST display an additional column:
|
||
- Header: `Dependencies`
|
||
- Value: `✅` or `—`
|
||
|
||
#### FR-COV-DEP-002 Deterministic derivation
|
||
The `Dependencies` value MUST be derived deterministically from existing capabilities (config/contracts) only:
|
||
|
||
`✅` if at least one holds:
|
||
- the type supports Assignments extraction, or
|
||
- the type supports Scope Tags, or
|
||
- the type can reference Assignment Filters, or
|
||
- the type has dependency extraction rules in Spec 042 (relationship taxonomy / extractor mapping)
|
||
|
||
Otherwise: `—`.
|
||
|
||
This is **feature support**, not “Graph supports $expand”.
|
||
|
||
MVP decision:
|
||
- For foundation types, default to `—`.
|
||
|
||
### FR-005 Inventory Items UI
|
||
Inventory Items list MUST allow:
|
||
- filtering to Foundations (e.g., Category = Foundations)
|
||
- searching by display name
|
||
- viewing details (existing view)
|
||
|
||
### FR-006 No extra Graph calls in UI
|
||
The UI MUST NOT perform Graph lookups for foundation name resolution. Resolution MUST come from local InventoryItems.
|
||
|
||
This MUST be enforced by an automated test that fails if any UI rendering/resolution path calls `GraphClientInterface`.
|
||
|
||
## Non-Functional Requirements
|
||
|
||
### NFR-001 Tenant isolation
|
||
All reads/writes MUST be tenant-scoped and covered by tests.
|
||
|
||
### NFR-002 Data minimization
|
||
Foundation sync MUST store only a safe subset of metadata consistent with Inventory rules:
|
||
- For any stored InventoryItem, `meta_jsonb` MUST equal `InventoryMetaSanitizer::sanitize(meta_jsonb)`.
|
||
- `json_encode(meta_jsonb)` MUST NOT contain `Bearer `.
|
||
|
||
### NFR-003 Idempotency
|
||
Re-running foundation sync MUST be idempotent (no duplicates) and update `last_seen_at`/`last_seen_run_id` deterministically.
|
||
|
||
The sync MUST NOT delete InventoryItem rows when objects are not observed in a run; absence is treated as “not observed” (e.g., permission/scope/transient failure) and becomes stale via `last_seen_at`/run evaluation.
|
||
|
||
### NFR-004 Observability
|
||
Sync run record MUST be accurate:
|
||
- counts include foundations when `include_foundations=true`
|
||
- warnings/errors are persisted on the run record as per Inventory conventions
|
||
|
||
## Success Criteria
|
||
|
||
- SC1: After a foundations-enabled sync, dependencies for scope_tag/assignment_filter render as resolved for the majority of items that reference them.
|
||
- SC2: Inventory Coverage clearly communicates what is supported for “Policies” vs “Foundations”.
|
||
- SC3: No new permissions beyond existing foundation read scopes are required for this feature.
|
||
|
||
## Related Specs
|
||
|
||
- Core Inventory: `specs/040-inventory-core/spec.md`
|
||
- Inventory UI: `specs/041-inventory-ui/spec.md`
|
||
- Dependencies Graph: `specs/042-inventory-dependencies-graph/spec.md`
|
||
- Inventory Sync Button: `specs/046-inventory-sync-button/spec.md`
|