6.8 KiB
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)andjson_encode(meta_jsonb)must not containBearer. - 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=falsehide/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 vialast_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_typeis intenantpilot.supported_policy_types - Foundation Nodes: InventoryItems whose
policy_typeis intenantpilot.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_typesfor 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_typeset to the foundation type key (e.g.roleScopeTag,assignmentFilter,notificationMessageTemplate)categoryset to the literal stringFoundations(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-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_jsonbMUST equalInventoryMetaSanitizer::sanitize(meta_jsonb). json_encode(meta_jsonb)MUST NOT containBearer.
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