TenantAtlas/specs/047-inventory-foundations-nodes/spec.md
2026-01-10 20:59:16 +01:00

6.8 KiB
Raw Blame History

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 items 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-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.
  • 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