TenantAtlas/specs/163-baseline-subject-resolution/data-model.md
ahmido c17255f854 feat: implement baseline subject resolution semantics (#193)
## Summary
- add the structured subject-resolution foundation for baseline compare and baseline capture, including capability guards, subject descriptors, resolution outcomes, and operator action categories
- persist structured evidence-gap subject records and update compare/capture surfaces, landing projections, and cleanup tooling to use the new contract
- add Spec 163 artifacts and focused Pest coverage for classification, determinism, cleanup, and DB-only rendering

## Validation
- `vendor/bin/sail bin pint --dirty --format agent`
- `vendor/bin/sail artisan test --compact tests/Unit/Support/Baselines tests/Feature/Baselines tests/Feature/Filament/OperationRunEnterpriseDetailPageTest.php`

## Notes
- verified locally that a fresh post-restart baseline compare run now writes structured `baseline_compare.evidence_gaps.subjects` records instead of the legacy broad payload shape
- excluded the separate `docs/product/spec-candidates.md` worktree change from this branch commit and PR

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #193
2026-03-25 12:40:45 +00:00

168 lines
8.5 KiB
Markdown

# Data Model: Baseline Subject Resolution and Evidence Gap Semantics Foundation
## Overview
This feature does not require a new primary database table. It introduces a richer logical model for subject classification and resolution, then persists that model inside existing compare and capture run context for new runs. Development-only run payloads using the old broad reason shape may be removed or regenerated instead of preserved through a compatibility contract.
## Entity: SubjectDescriptor
Business-level descriptor for a compare or capture target before local resolution.
### Fields
| Field | Type | Required | Description |
|---|---|---|---|
| `policy_type` | string | yes | Canonical policy or foundation type from support metadata |
| `subject_external_id` | string | no | Stable local or tenant-local external identifier when available |
| `subject_key` | string | yes | Deterministic comparison key used across capture and compare |
| `subject_class` | enum | yes | One of `policy_backed`, `inventory_backed`, `foundation_backed`, `derived` |
| `resolution_path` | enum | yes | Intended local path, such as `policy`, `inventory`, `foundation_policy`, `foundation_inventory`, or `derived` |
| `support_mode` | enum | yes | `supported`, `limited`, `excluded`, or `invalid_support_config` |
| `source_model_expected` | enum | no | Which local model is expected to satisfy the lookup |
### Validation Rules
- `policy_type` must exist in canonical support metadata.
- `subject_class` must be one of the supported subject-class values.
- `resolution_path` must be compatible with `subject_class`.
- `support_mode=invalid_support_config` is only valid when metadata claims support but no truthful runtime path exists.
## Entity: ResolutionOutcomeRecord
Deterministic result of attempting to resolve a `SubjectDescriptor` against tenant-local state.
### Fields
| Field | Type | Required | Description |
|---|---|---|---|
| `resolution_outcome` | enum | yes | Precise outcome such as `resolved_policy`, `resolved_inventory`, `policy_record_missing`, `inventory_record_missing`, `foundation_inventory_only`, `resolution_type_mismatch`, `unresolvable_subject`, `permission_or_scope_blocked`, `retryable_capture_failure`, `capture_failed`, `throttled`, or `budget_exhausted` |
| `reason_code` | string | yes | Stable operator-facing reason family persisted with the run |
| `operator_action_category` | enum | yes | `none`, `retry`, `run_inventory_sync`, `run_policy_sync_or_backup`, `review_permissions`, `inspect_subject_mapping`, or `product_follow_up` |
| `structural` | boolean | yes | Whether the outcome is structural rather than operational or transient |
| `retryable` | boolean | yes | Whether retry may change the outcome without prerequisite changes |
| `source_model_found` | enum or null | no | Which local model actually satisfied the lookup when resolution succeeded |
### State Families
| Family | Outcomes |
|---|---|
| `resolved` | `resolved_policy`, `resolved_inventory` |
| `structural` | `foundation_inventory_only`, `resolution_type_mismatch`, `unresolvable_subject`, `invalid_support_config` |
| `operational` | `policy_record_missing`, `inventory_record_missing`, `permission_or_scope_blocked`, `ambiguous_match`, `invalid_subject`, `duplicate_subject` |
| `transient` | `retryable_capture_failure`, `throttled`, `budget_exhausted`, `capture_failed` |
### Validation Rules
- `resolution_outcome` must map to exactly one state family.
- `structural=true` is only valid for structural state-family outcomes.
- `retryable=true` is only valid for transient outcomes or explicitly retryable operational outcomes.
## Entity: SupportCapabilityRecord
Runtime truth contract for whether a subject type may enter baseline compare or capture.
### Fields
| Field | Type | Required | Description |
|---|---|---|---|
| `policy_type` | string | yes | Canonical type key |
| `subject_class` | enum | yes | Dominant subject class for the type |
| `compare_capability` | enum | yes | `supported`, `limited`, or `unsupported` |
| `capture_capability` | enum | yes | `supported`, `limited`, or `unsupported` |
| `resolution_path` | enum | yes | Truthful runtime resolution path |
| `config_supported` | boolean | yes | Whether metadata claims support |
| `runtime_valid` | boolean | yes | Whether the runtime can honor that support claim |
### Validation Rules
- `config_supported=true` and `runtime_valid=false` must be surfaced as `invalid_support_config` rather than silently ignored.
- Types with `compare_capability=unsupported` must not enter compare scope.
- Types with `capture_capability=unsupported` must not enter capture execution.
## Entity: EvidenceGapDetailRecord
Structured subject-level record persisted under compare or capture run context for new runs.
### Fields
| Field | Type | Required | Description |
|---|---|---|---|
| `policy_type` | string | yes | Canonical type |
| `subject_external_id` | string or null | no | Stable identifier when available |
| `subject_key` | string | yes | Deterministic subject identity |
| `subject_class` | enum | yes | Classified subject class |
| `resolution_path` | enum | yes | Path attempted or declared |
| `resolution_outcome` | enum | yes | Deterministic resolution result |
| `reason_code` | string | yes | Stable reason family |
| `operator_action_category` | enum | yes | Recommended next action family |
| `structural` | boolean | yes | Structural versus non-structural marker |
| `retryable` | boolean | yes | Retryability marker |
| `source_model_expected` | enum or null | no | Expected local evidence model |
| `source_model_found` | enum or null | no | Actual local evidence model when present |
### Storage Locations
- `operation_runs.context.baseline_compare.evidence_gaps.subjects[]`
- `operation_runs.context.baseline_capture.gaps.subjects[]` or equivalent capture-context namespace
### Validation Rules
- New-run records must store structured objects, not only string subject tokens.
- `subject_key` must be deterministic for identical inputs.
- `reason_code` and `resolution_outcome` must not contradict each other.
- Old development rows that omit the new fields are cleanup candidates and should be regenerated or deleted rather than treated as a first-class runtime shape.
## Derived Entity: EvidenceGapProjection
Read-model projection used by canonical run-detail and tenant review surfaces.
### Fields
| Field | Type | Description |
|---|---|---|
| `detail_state` | enum | `no_gaps`, `structured_details_recorded`, `details_not_recorded`, `legacy_broad_reason` |
| `count` | integer | Total gap count |
| `by_reason` | map<string,int> | Aggregate counts by reason |
| `recorded_subjects_total` | integer | Number of structured subject rows available for projection |
| `missing_detail_count` | integer | Gap count that has no structured row attached |
| `structural_count` | integer | Number of recorded structural gap rows |
| `operational_count` | integer | Number of recorded non-structural, non-retryable rows |
| `transient_count` | integer | Number of recorded retryable rows |
| `legacy_mode` | boolean | Indicates the run still stores a broad legacy gap payload |
| `buckets` | list | Grouped records by reason with searchable row payload |
| `requires_regeneration` | boolean | Whether stale local development data should be regenerated rather than interpreted semantically |
## State Transitions
### Resolution lifecycle for a subject
1. `described`
- `SubjectDescriptor` is created from scope, metadata, and capability information.
2. `validated`
- Runtime support guard confirms whether the subject may enter compare or capture.
3. `resolved`
- The system attempts the appropriate local path and emits a `ResolutionOutcomeRecord`.
4. `persisted`
- New runs store the structured `EvidenceGapDetailRecord` or resolved outcome details in `OperationRun.context`.
5. `projected`
- Existing operator surfaces consume the new structured projection. Stale development data is regenerated or removed instead of driving a permanent compatibility path.
## Example New-Run Compare Gap Record
```json
{
"policy_type": "roleScopeTag",
"subject_external_id": "42f4fcb8-5b35-44ec-9240-0a1ad7c31fb1",
"subject_key": "rolescopetag|42f4fcb8-5b35-44ec-9240-0a1ad7c31fb1",
"subject_class": "foundation_backed",
"resolution_path": "foundation_inventory",
"resolution_outcome": "foundation_inventory_only",
"reason_code": "foundation_not_policy_backed",
"operator_action_category": "product_follow_up",
"structural": true,
"retryable": false,
"source_model_expected": "inventory_item",
"source_model_found": "inventory_item"
}
```