# Research: Governance Subject Taxonomy and Baseline Scope V2 ## Decision: Evolve the existing `BaselineScope` entrypoint into the V2-aware scope orchestration layer ### Rationale `BaselineProfile::scopeJsonb()` already normalizes persisted scope through `BaselineScope`, and both `BaselineCaptureService` and `BaselineCompareService` resolve their effective scope through the same entrypoint. Reusing that integration point keeps the rollout narrow and avoids a split where legacy and V2 scope models coexist as competing top-level contracts. ### Alternatives considered - Introduce a parallel `BaselineScopeV2` class and migrate consumers later: rejected because it would duplicate the current integration boundary and make rollout ambiguity worse before it gets better. - Leave `BaselineScope` untouched and normalize only in the model cast: rejected because capture, compare, tests, and operation context already rely on `BaselineScope` behavior outside the model cast. ## Decision: Build the taxonomy registry by composing existing config and support metadata contributors ### Rationale The current truth for selectable baseline subjects already exists in three places: `tenantpilot.supported_policy_types`, `tenantpilot.foundation_types`, and `InventoryPolicyTypeMeta::baselineSupportContract()`. The narrowest registry is therefore a consolidation layer that reads and shapes those contributors into one authoritative contract for baseline selection. ### Alternatives considered - Add a new config file dedicated to governance subjects: rejected because it would create a second manual source of truth for the same policy and foundation types. - Hardcode baseline subject mappings inside a registry class: rejected because it would duplicate labels, support flags, and future updates already owned by config and existing support metadata. ## Decision: Introduce platform-facing taxonomy vocabulary separate from the current baseline support enums ### Rationale The existing `SubjectClass` and `ResolutionPath` enums describe support resolution internals such as `policy_backed` and `foundation_inventory`. They do not describe operator-facing governed-subject shape. Reusing them as the new taxonomy would leak implementation semantics into the platform contract. ### Alternatives considered - Reuse `SubjectClass` as the new platform taxonomy: rejected because `policy_backed`, `foundation_backed`, and `derived` encode comparison internals rather than stable platform vocabulary. - Collapse taxonomy and support metadata into one enum family: rejected because operation support and governed-subject shape are related but distinct concerns. ## Decision: Map current Intune policy types to `intune/policy` and current foundations to `platform_foundation/configuration_resource` ### Rationale The current baseline scope has two real buckets: standard Intune policy types and non-policy foundation artifacts. Mapping the first bucket to `domain_key = intune` and `subject_class = policy` keeps the Intune adapter explicit. Mapping the second bucket to `domain_key = platform_foundation` and `subject_class = configuration_resource` removes the unnamed special category while staying broad enough to cover the concrete current foundation artifacts such as assignment filters, scope tags, notification templates, and Intune RBAC definitions. ### Alternatives considered - Map foundations under the `intune` domain: rejected because it would keep policy and non-policy baseline subjects collapsed inside the adapter taxonomy that this spec is trying to make explicit. - Use `posture_dimension` for all current foundation types: rejected because the current concrete foundation artifacts are configuration resources, not derived posture dimensions. ## Decision: Use tolerant read plus save-forward as the rollout strategy ### Rationale The current repo has broad test and feature coverage that writes legacy `scope_jsonb` shapes directly. Tolerant read plus save-forward allows those rows to remain valid while new or updated baseline profiles become canonical V2 immediately. This lowers rollout risk and avoids an all-at-once migration event. ### Alternatives considered - Eagerly rewrite all existing rows in a migration: rejected because it increases rollout churn and couples semantic correction to data-rewrite risk. - Leave legacy and V2 rows mixed indefinitely: rejected because it would preserve two canonical shapes instead of one. ## Decision: Keep the baseline UI Intune-first and derive V2 internally ### Rationale The feature is a contract upgrade, not a multi-domain product launch. The current Filament baseline profile form already exposes `policy_types` and `foundation_types` separately. Keeping that operator-facing shape for now avoids false breadth while still allowing save-forward persistence into explicit V2 entries and better normalized summaries. ### Alternatives considered - Replace the form with a multi-domain taxonomy picker now: rejected because only Intune-backed baseline subjects are currently ready for operator use. - Keep the UI and continue persisting legacy arrays: rejected because the persistence contract is the core defect this spec is solving. ## Decision: Persist canonical effective scope in operation context and keep compatibility projections only as a transition aid ### Rationale `BaselineCaptureService` and `BaselineCompareService` already write `effective_scope` into `OperationRun.context`. Making that payload canonical V2 improves auditability and debug clarity. Some legacy-compatible projections may still be needed by current surfaces or tests during rollout, but they should be derived from V2 instead of remaining authoritative. ### Alternatives considered - Keep only the legacy context payload: rejected because it would leave operation auditability tied to the very ambiguity this spec is removing. - Emit only V2 immediately with no compatibility projection: rejected because current code and tests may still read the old keys during the transition. ## Decision: Merge duplicate entries only when they are semantically identical and reject ambiguous overlaps otherwise ### Rationale Legacy scope behaves like a union of subject types. V2 should preserve that determinism without hiding ambiguous entry definitions. If two entries share the same `domain_key`, `subject_class`, and normalized `filters`, their `subject_type_keys` can be merged safely. If overlapping subject types appear across entries with different filters, the system should reject the payload until filter semantics are explicitly supported. ### Alternatives considered - Always merge duplicate entries regardless of filters: rejected because it would silently flatten distinct meanings once filters become real. - Reject every repeated entry shape: rejected because the legacy shape and current operator intent behave like set union, not strict uniqueness by payload object. ## Decision: Deliver cleanup as an optional Artisan command rather than a mandatory migration step ### Rationale The product only needs one canonical shape going forward. Rewriting historic rows is housekeeping, not a functional prerequisite. An optional command keeps that concern explicit and schedulable after rollout confidence exists. To remain aligned with the constitution's write-safety rules, the command should default to preview mode, require explicit write confirmation for committed rewrites, and emit audit entries only when a real mutation occurs. This cleanup decision applies only to `baseline_profiles.scope_jsonb` in this release. Tenant assignment overrides stay on tolerant-read normalization so the rollout remains narrow and compare behavior stays correct without introducing a second maintenance rewrite surface. ### Alternatives considered - Omit cleanup entirely: rejected because the repo should still have a supported path to remove legacy rows once the rollout stabilizes. - Hide cleanup inside a request path or scheduled job: rejected because schema-shape rewrites should remain a deliberate operator or maintenance action with explicit write intent and auditable consequences.