TenantAtlas/specs/202-governance-subject-taxonomy/research.md

102 lines
8.0 KiB
Markdown

# 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.