# Phase 1 Data Model: Inventory Coverage Truth (177) ## Existing Persisted Truth ### `OperationRun` as coverage basis Represents the canonical execution record for inventory syncs and remains the source of per-type sync truth. **Relevant existing fields** - `workspace_id` - `tenant_id` - `type` - `status` - `outcome` - `summary_counts` - `failure_summary` - `context` - `started_at` - `completed_at` **Relevant existing inventory context shape** - `context.inventory.coverage.policy_types` - `context.inventory.coverage.foundation_types` - each entry stores at minimum `status` - optional fields already supported by `InventoryCoverage` normalization: - `item_count` - `error_code` **Existing invariant** - `InventoryCoverage::fromContext()` is the canonical parser for run coverage payload. ### `InventoryItem` Represents last observed tenant inventory rows and remains the source of current observed-item counts. **Relevant existing fields** - `workspace_id` - `tenant_id` - `policy_type` - `external_id` - `display_name` - `category` - `platform` - `meta_jsonb` - `last_seen_at` - `last_seen_operation_run_id` **Existing invariant** - Observed rows prove last observation only. They do not by themselves prove current tenant coverage completeness. ### `InventoryPolicyTypeMeta` + capability metadata Represents product support and capability reference for supported and foundation types. **Relevant existing fields and derived attributes** - `type` - `label` - `category` - `platform` - `restore` - `risk` - foundation flag - dependency support from `CoverageCapabilitiesResolver` **Existing invariant** - Capability metadata is product support truth, not tenant coverage truth. ## New Derived Runtime Contract ### `TenantCoverageTruth` Derived runtime contract that answers the operator question: which supported types are currently covered for this tenant, which types need follow-up, and which run establishes that statement. **Proposed fields** - `tenant_id` - `basis_run_id` nullable - `basis_run_outcome` nullable - `basis_completed_at` nullable - `has_current_coverage_result` boolean - `supported_type_count` - `succeeded_type_count` - `failed_type_count` - `skipped_type_count` - `unknown_type_count` - `follow_up_type_count` - `observed_item_total` - `rows` list of `TenantCoverageTypeTruth` **Derived invariants** - Exactly one row exists for each supported policy type and foundation type currently in the product support catalog. - `follow_up_type_count = failed + skipped + unknown`. - `has_current_coverage_result` is true only when a completed inventory-sync basis run with parseable payload exists. - The basis run is chosen independently from current item counts. ### `TenantCoverageTypeTruth` Derived row contract for one supported type. **Proposed fields** - `type` - `segment` (`policy` or `foundation`) - `label` - `category` - `platform` nullable - `coverage_state` (`succeeded`, `failed`, `skipped`, `unknown`) - `follow_up_required` boolean - `observed_item_count` - `basis_error_code` nullable - `restore_mode` nullable - `risk_level` nullable - `supports_dependencies` boolean **Derived invariants** - `follow_up_required` is true for `failed`, `skipped`, and `unknown`; false only for `succeeded`. - `observed_item_count > 0` does not change `coverage_state`. - `coverage_state = unknown` when the type is supported but absent from the basis run payload. - `basis_error_code` is allowed only for non-succeeded payload-backed states. ## Derived State Family ### Coverage state family This feature introduces one derived state family for tenant coverage rows: - `Succeeded` - the basis run reported the type as successfully processed - `Failed` - the basis run reported the type as attempted and failed - `Skipped` - the basis run reported the type as intentionally skipped or not processed during that run - `Unknown` - no current coverage result exists for the supported type in the basis run **Behavioral consequence** - `Failed`, `Skipped`, and `Unknown` all suppress calm claims and increment follow-up counts. - `Unknown` is derived, not persisted. ## Relationships - One `TenantCoverageTruth` resolves for one tenant at a time. - One `TenantCoverageTruth` may reference zero or one basis `OperationRun`. - One `TenantCoverageTruth` contains one row per supported product type from `InventoryPolicyTypeMeta::supported()` and `InventoryPolicyTypeMeta::foundations()`. - Each `TenantCoverageTypeTruth` joins one supported type to zero or one payload-backed status from the basis run and zero or more `InventoryItem` rows from the current tenant observation set. ## Selection Rules ### Basis run selection - candidate runs are `OperationRun` rows where: - `tenant_id` matches the selected tenant - `type = inventory_sync` - `status = completed` - candidates are ordered by: - `completed_at DESC` - `id DESC` - the selected basis run is the first candidate whose `context.inventory.coverage` payload can be parsed by `InventoryCoverage::fromContext()` - if no candidate qualifies, the tenant has no current coverage basis run ### Unknown derivation - if a supported type is absent from both `policy_types` and `foundation_types` in the selected basis payload, the type is `Unknown` - absence from the basis payload is not converted into `Skipped` - item presence from older runs does not upgrade `Unknown` ## Validation Rules - No schema migration is required. - No new persisted state is introduced. - Coverage rows must remain tenant-scoped. - Capability metadata must not alter the derived coverage state. - Surfaces may cite the basis run only when they can do so without violating authorization.