## Summary - introduce the governance subject taxonomy registry and canonical Baseline Scope V2 normalization and persistence - update baseline profile Filament surfaces, validation, capture/compare gating, and add the optional scope backfill command with audit logging - add focused unit, feature, Filament, and browser smoke coverage for save-forward behavior, operation truth, authorization continuity, and invalid-scope rendering - remove the duplicate legacy spec plan under `specs/001-governance-subject-taxonomy/plan.md` ## Verification - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec202GovernanceSubjectTaxonomySmokeTest.php` - focused Spec 202 regression pack: `56 passed (300 assertions)` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` ## Notes - no schema migration required - no new Filament asset registration required - branch includes the final browser smoke test coverage for the current feature Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #232
239 lines
11 KiB
Markdown
239 lines
11 KiB
Markdown
# 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<string> | Required non-empty set of subject type keys |
|
|
| `filters` | map<string, mixed> | 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<BaselineScopeEntryV2> | 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<string> | Empty or omitted means all supported Intune policy subject types when legacy input is otherwise present |
|
|
| `foundation_types` | array<string> | 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<string> | Flattened selected subject type keys |
|
|
| `allowed_type_keys` | array<string> | Types eligible for the intended operation |
|
|
| `limited_type_keys` | array<string> | Types that run with limited support semantics |
|
|
| `unsupported_type_keys` | array<string> | Types rejected for the intended operation |
|
|
| `capabilities_by_type` | map<string, mixed> | Existing support metadata exposed for debugging and audit |
|
|
| `legacy_projection` | map<string, array<string>> 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<string> | 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<string> | 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. |