# Data Model: Governance Subject Taxonomy and Baseline Scope V2 ## Overview This feature introduces no new persisted entity. It reuses existing baseline scope storage and operation context storage, but replaces the internal canonical meaning of baseline scope with a versioned V2 document backed by a governance subject taxonomy registry. ## Existing Source Truths Reused Without Change ### Baseline profile persistence The following existing persisted fields remain authoritative and are not moved into a new table: - `baseline_profiles.scope_jsonb` - `baseline_tenant_assignments.override_scope_jsonb` - `operation_runs.context.effective_scope` This feature changes how those payloads are normalized and interpreted, not where they live. For rollout closure in this release, only `baseline_profiles.scope_jsonb` is eligible for optional cleanup rewrite. `baseline_tenant_assignments.override_scope_jsonb` remains tolerant-read and compare-only normalization state. ### Taxonomy contributors already present in the repo The following current contributors remain the underlying source material for the new registry: - `config('tenantpilot.supported_policy_types')` - `config('tenantpilot.foundation_types')` - `InventoryPolicyTypeMeta::baselineSupportContract()` - `InventoryPolicyTypeMeta::baselineCompareLabel()` and related metadata helpers ### Existing operation truth reused without change - `baseline_capture` remains the canonical capture operation type - `baseline_compare` remains the canonical compare operation type - existing audit, authorization, and queued-run behavior remain unchanged ## New Canonical Contracts ### GovernanceDomainKey **Type**: code enum or equivalent value object **Purpose**: identify the governance domain that owns a subject type | Value | Status | Notes | |------|--------|-------| | `intune` | active | Current Intune policy subject families | | `platform_foundation` | active | Current non-policy foundation subject families used by baselines | | future values | reserved | Later domains such as Entra or Teams may be added without changing the V2 shape | ### GovernanceSubjectClass **Type**: code enum or equivalent value object **Purpose**: describe the platform-level shape of a governed subject | Value | Status | Notes | |------|--------|-------| | `policy` | active | Current Intune policy types | | `configuration_resource` | active | Current baseline foundation artifacts | | `posture_dimension` | reserved | Future non-policy posture dimensions | | `control` | reserved | Future control-oriented subject families | This is intentionally separate from the existing baseline support `SubjectClass` enum because that older enum encodes resolution behavior rather than platform-facing taxonomy. ### GovernanceSubjectType **Type**: derived registry record **Source**: config contributors plus existing support metadata | Field | Type | Notes | |------|------|-------| | `domain_key` | string | `GovernanceDomainKey` value | | `subject_class` | string | `GovernanceSubjectClass` value | | `subject_type_key` | string | Domain-owned leaf type discriminator | | `label` | string | Operator-facing label | | `description` | string or null | Short operator or admin explanation | | `capture_supported` | boolean | Whether baseline capture may include this subject type | | `compare_supported` | boolean | Whether baseline compare may include this subject type | | `inventory_supported` | boolean | Whether inventory-backed browsing exists for this type | | `active` | boolean | Whether the type is currently selectable | | `support_mode` | string | Derived from existing support contract for audit and validation detail | | `legacy_bucket` | string or null | Transitional mapping back to `policy_types` or `foundation_types` when required | ### GovernanceSubjectTaxonomyRegistry **Type**: in-process registry contract **Source**: composed from the existing config and support contributors Required lookup behaviors: - list active baseline-selectable subject types - lookup one subject type by `domain_key + subject_type_key` - validate whether a subject class is legal for a given domain - resolve operation support flags for capture and compare - provide operator-safe label and description metadata ### BaselineScopeEntryV2 **Type**: canonical scope selector record | Field | Type | Notes | |------|------|-------| | `domain_key` | string | Required governance domain | | `subject_class` | string | Required platform-level subject class | | `subject_type_keys` | array | Required non-empty set of subject type keys | | `filters` | map | Optional; empty for current Intune behavior | Normalization rules: - `subject_type_keys` are deduplicated and sorted - entries with the same `domain_key`, `subject_class`, and normalized `filters` may be merged by unioning `subject_type_keys` - overlapping subject type keys across entries with different filters are rejected as ambiguous until filter semantics are explicitly supported ### BaselineScopeDocumentV2 **Type**: canonical baseline scope document | Field | Type | Notes | |------|------|-------| | `version` | integer | Must equal `2` | | `entries` | array | Non-empty array of canonical selectors | Semantics: - the document is explicit; defaults are resolved before persistence - no entry may rely on implicit Intune-only meaning - the document is the only canonical persisted form for new or updated baseline profiles ### LegacyBaselineScopePayload **Type**: ingestion-only compatibility payload | Field | Type | Notes | |------|------|-------| | `policy_types` | array | Empty or omitted means all supported Intune policy subject types when legacy input is otherwise present | | `foundation_types` | array | Empty or omitted means no foundations when legacy input is otherwise present | Mapping rules: - `policy_types` normalize to one V2 entry with `domain_key = intune` and `subject_class = policy` - `foundation_types` normalize to one V2 entry with `domain_key = platform_foundation` and `subject_class = configuration_resource` - a legacy payload with one missing bucket normalizes the missing bucket using the same semantics as its empty-list default - a legacy payload with neither bucket present is invalid and must be rejected before normalization - a mixed payload containing both legacy fields and explicit V2 fields is rejected ### EffectiveBaselineScope **Type**: derived operation-start contract **Source**: canonical profile scope + compare-assignment override when applicable + operation support gating | Field | Type | Notes | |------|------|-------| | `canonical_scope` | `BaselineScopeDocumentV2` | The effective canonical scope after compare override narrowing when applicable | | `selected_type_keys` | array | Flattened selected subject type keys | | `allowed_type_keys` | array | Types eligible for the intended operation | | `limited_type_keys` | array | Types that run with limited support semantics | | `unsupported_type_keys` | array | Types rejected for the intended operation | | `capabilities_by_type` | map | Existing support metadata exposed for debugging and audit | | `legacy_projection` | map> or null | Transitional projection back to legacy buckets for current consumers only | ### BaselineScopeSummaryGroup **Type**: derived operator-facing summary record | Field | Type | Notes | |------|------|-------| | `domain_key` | string | Group identity | | `subject_class` | string | Group identity | | `group_label` | string | Operator-facing summary label | | `selected_subject_types` | array | Operator-facing selected labels in the group, not raw subject type keys | | `capture_supported_count` | integer | Number of types capture may include | | `compare_supported_count` | integer | Number of types compare may include | | `inactive_count` | integer | Number of stored but inactive types, if historic data is being inspected | ### BaselineScopeNormalizationLineage **Type**: derived diagnostic record for on-demand detail rendering | Field | Type | Notes | |------|------|-------| | `source_shape` | string | One of `legacy` or `canonical_v2` | | `normalized_on_read` | boolean | Whether tolerant-read normalization was required for the current payload | | `legacy_keys_present` | array | Which legacy keys were present at ingestion time, if any | | `save_forward_required` | boolean | Whether the current payload still needs save-forward persistence to become canonical | ## Validation Rules ### Canonical V2 validation 1. `version` must equal `2`. 2. `entries` must be present and non-empty. 3. Each entry must contain a valid domain and a valid subject class for that domain. 4. Each entry must contain at least one subject type key. 5. Every subject type key must belong to the specified domain and subject class. 6. Unknown or inactive subject type keys fail validation. 7. Duplicate entries are merged only when semantically identical after normalization. 8. Mixed legacy and V2 payloads fail validation. ### Operation validation 1. Capture start rejects any effective scope containing subject types without capture support. 2. Compare start rejects any effective scope containing subject types without compare support. 3. Invalid support metadata is treated as unsupported. 4. Operation context stores the resolved effective scope used for the run, not the pre-normalized request payload. ## Relationships - One `GovernanceSubjectTaxonomyRegistry` yields many `GovernanceSubjectType` records. - One `BaselineScopeDocumentV2` contains one or more `BaselineScopeEntryV2` records. - One `BaselineProfile` owns one persisted baseline scope document inside `scope_jsonb`. - One `BaselineTenantAssignment` may contribute an override scope that narrows the profile scope before compare start. - One `EffectiveBaselineScope` is derived for each capture or compare start attempt. - One `BaselineScopeSummaryGroup` is derived from one canonical scope document for operator-facing baseline surfaces. - One `BaselineScopeNormalizationLineage` is derived alongside normalized scope and exposed only on demand for detail-surface diagnostics. ## Transition Rules ### Legacy to canonical V2 1. Read legacy `scope_jsonb`. 2. Expand legacy defaults explicitly. 3. Map policy and foundation buckets into V2 entries. 4. Validate against the taxonomy registry. 5. Persist canonical V2 on the next successful save. ### Canonical V2 to operation context 1. Start from the canonical profile scope. 2. Apply any compare assignment override scope as a narrowing step when the operation supports it. 3. Flatten selected subject type keys. 4. Run capture or compare support gating. 5. Write canonical effective scope plus any temporary compatibility projection into `OperationRun.context`. ### Optional backfill 1. Select baseline profile rows still storing legacy scope shape in `baseline_profiles.scope_jsonb`. 2. Preview candidate rewrites by default and report which rows would change without mutating persisted data. 3. Require explicit write confirmation before persisting canonical V2 back into `scope_jsonb`. 4. Write audit entries for committed rewrites with actor and before-or-after mutation context appropriate to workspace-owned baseline profiles. 5. Leave already-canonical V2 profile rows untouched. 6. Leave `baseline_tenant_assignments.override_scope_jsonb` on tolerant-read normalization only in this release.