TenantAtlas/specs/168-tenant-governance-aggregate-contract/data-model.md
ahmido 807d574d31 feat: add tenant governance aggregate contract and action surface follow-ups (#199)
## Summary
- amend the operator UI constitution and related SpecKit templates for the new UI/UX governance rules
- add Spec 168 artifacts plus the tenant governance aggregate implementation used by the tenant dashboard, banner, and baseline compare landing surfaces
- normalize Filament action surfaces around clickable-row inspection, grouped secondary actions, and explicit action-surface declarations across enrolled resources and pages
- fix post-suite regressions in membership cache priming, finding workflow state refresh, tenant review derived-state invalidation, and tenant-bound backup-set related navigation

## Commit Series
- `docs: amend operator UI constitution`
- `spec: add tenant governance aggregate contract`
- `feat: add tenant governance aggregate contract`
- `refactor: normalize filament action surfaces`
- `fix: resolve post-suite state regressions`

## Testing
- `vendor/bin/sail artisan test --compact`
- Result: `3176 passed, 8 skipped (17384 assertions)`

## Notes
- Livewire v4 / Filament v5 stack remains unchanged
- no provider registration changes; `bootstrap/providers.php` remains the relevant location
- no new global-search resources or asset-registration changes in this branch

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #199
2026-03-29 21:14:17 +00:00

213 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 1 Data Model: Tenant Governance Aggregate Contract
## Overview
This feature does not add a database table or persisted summary artifact. It formalizes the existing persistent source truths that already drive tenant governance posture and adds one derived runtime contract plus request-scoped reuse rules for the shared summary family.
## Persistent Source Truths
### Tenant
**Purpose**: The tenant is the scope boundary for the aggregate and for every covered surface.
**Key fields**:
- `id`
- `workspace_id`
- `external_id`
**Validation rules**:
- Aggregate resolution is allowed only for one explicit tenant scope at a time.
- Workspace membership and tenant entitlement remain authoritative before any summary surface renders.
### BaselineTenantAssignment and BaselineProfile
**Purpose**: Define whether the tenant has an assigned baseline and which profile and snapshot chain determine compare availability.
**Key fields**:
- `baseline_tenant_assignments.tenant_id`
- `baseline_tenant_assignments.baseline_profile_id`
- `baseline_profiles.id`
- `baseline_profiles.name`
- `baseline_profiles.active_snapshot_id`
**Validation rules**:
- Missing assignment and missing snapshot remain derived availability states, not new persisted governance states.
### BaselineSnapshot
**Purpose**: Supplies consumable compare-snapshot truth for the assigned baseline profile.
**Key fields**:
- `id`
- `baseline_profile_id`
- lifecycle/completion fields already used by `BaselineSnapshotTruthResolver`
**Validation rules**:
- Snapshot usability remains governed by existing compare truth logic.
- The aggregate must not invent a second snapshot-availability rule set.
### OperationRun
**Purpose**: Supplies baseline-compare progress, completion, failure, and freshness context.
**Key fields**:
- `id`
- `tenant_id`
- `workspace_id`
- `type`
- `status`
- `outcome`
- `completed_at`
- `context`
**Validation rules**:
- Only existing baseline-compare runs influence compare posture in this slice.
- The aggregate does not introduce a new run type or a second operational state model.
### Finding and FindingException
**Purpose**: Supply overdue workflow state, visible drift pressure, and accepted-risk governance validity.
**Key fields**:
- `findings.tenant_id`
- `findings.status`
- `findings.severity`
- `findings.due_at`
- `finding_exceptions.current_validity_state`
**Validation rules**:
- Overdue, expiring, lapsed, active-non-new, and high-severity-active counts remain derived from current findings truth.
- The aggregate must not redefine accepted-risk validity semantics.
## Existing Runtime Source Objects
### BaselineCompareStats
**Purpose**: Existing query-backed compare truth object that already combines compare availability, diagnostics, and governance-attention counts for one tenant.
**Key fields consumed by this feature**:
- `state`
- `profileName`
- `operationRunId`
- `findingsCount`
- `lastComparedHuman`
- `lastComparedIso`
- `reasonCode`
- `overdueOpenFindingsCount`
- `expiringGovernanceCount`
- `lapsedGovernanceCount`
- `activeNonNewFindingsCount`
- `highSeverityActiveFindingsCount`
**Relationship to the new aggregate**:
- The aggregate is built from one `BaselineCompareStats` resolution.
- Landing diagnostics continue to read `BaselineCompareStats` directly.
### BaselineCompareSummaryAssessment
**Purpose**: Existing summary interpretation object that maps `BaselineCompareStats` to posture family, tone, headline, supporting message, and next-action target.
**Key fields consumed by this feature**:
- `stateFamily`
- `headline`
- `supportingMessage`
- `tone`
- `positiveClaimAllowed`
- `reasonCode`
- `nextActionTarget()`
- `nextActionLabel()`
**Relationship to the new aggregate**:
- The aggregate uses the summary assessment as its summary-semantics input.
- The feature must not fork a second state-family interpretation path.
## New Derived Runtime Entities
### TenantGovernanceAggregate
**Purpose**: One tenant-scoped operator summary contract that owns the shared posture, count family, and next-action intent for tenant-governance summary surfaces.
#### Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `tenant_id` | int | yes | Tenant scope for the aggregate |
| `workspace_id` | int | yes | Workspace scope for request-local reuse safety |
| `profile_name` | string nullable | no | Assigned baseline profile name when available |
| `compare_state` | string | yes | Existing compare availability or execution state from `BaselineCompareStats` |
| `state_family` | string | yes | Existing summary posture family from `BaselineCompareSummaryAssessment` |
| `tone` | string | yes | Existing tone family used by covered summary surfaces |
| `headline` | string | yes | Operator-facing summary headline |
| `supporting_message` | string nullable | no | Secondary operator-facing explanation |
| `reason_code` | string nullable | no | Summary reason code when available |
| `last_compared_label` | string nullable | no | Human-readable freshness label |
| `visible_drift_findings_count` | int | yes | Visible drift findings count from compare stats |
| `overdue_open_findings_count` | int | yes | Overdue open findings count |
| `expiring_governance_count` | int | yes | Accepted-risk governance nearing expiry |
| `lapsed_governance_count` | int | yes | Accepted-risk governance no longer valid |
| `active_non_new_findings_count` | int | yes | Active non-new findings pressure |
| `high_severity_active_findings_count` | int | yes | High-severity active findings pressure |
| `next_action_label` | string | yes | Stable operator-facing next-step label |
| `next_action_target` | enum(`findings`,`run`,`landing`,`none`) | yes | Stable next-action intent; surfaces map this to local URLs |
| `positive_claim_allowed` | bool | yes | Whether the current summary posture qualifies as a trustworthy all-clear |
| `stats` | `BaselineCompareStats` | yes | Embedded source truth used when a consumer also needs compare diagnostics |
| `summary_assessment` | `BaselineCompareSummaryAssessment` | yes | Embedded summary truth used by all covered surfaces |
#### Validation rules
- The aggregate must be built from exactly one `BaselineCompareStats` instance and exactly one `BaselineCompareSummaryAssessment` derived from that stats instance.
- Count fields must be copied from the same stats instance; surfaces must not recompute them locally.
- Final URLs, local badges, and layout-specific copy remain outside the aggregate.
### TenantGovernanceAggregateResolver
**Purpose**: Service seam that resolves one `TenantGovernanceAggregate` per tenant scope and handles request-local reuse.
#### Fields / Inputs
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `tenant` | `Tenant` nullable | yes | Target tenant; nullable only for explicit no-tenant handling paths |
| `workspace_id` | int nullable | no | Optional explicit scope input for guardable key composition |
| `surface_variant` | string | yes | Stable consumer variant used when request-local reuse or guard declarations need surface-specific context |
#### Validation rules
- The resolver must stay derived-only.
- Reuse must be request-local only.
- A no-tenant or wrong-tenant path must never reuse a previous tenants aggregate.
### DerivedStateFamily::TenantGovernanceAggregate
**Purpose**: Extends the existing Spec 167 request-scoped derived-state contract so the aggregate follows the same consumer-declaration and guard rules as other supported deterministic families.
#### Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `family` | enum value | yes | Stable family identifier for request-local aggregate reuse |
| `default_freshness_policy` | enum | yes | Expected to remain `invalidate_after_mutation` for landing refresh paths |
| `allows_negative_result_cache` | bool | yes | Aggregate resolution should be reusable for deterministic no-tenant or unavailable states only if the final key contract explicitly supports it |
## Consumer Mapping
| Consumer | Aggregate responsibility | Local responsibility |
|---|---|---|
| `NeedsAttention` | Overdue, expiring, lapsed, high-severity counts; baseline posture headline; next-action intent | Operations-in-progress count and widget-specific healthy fallback layout |
| `BaselineCompareNow` | Baseline posture family, headline, supporting message, next-action intent | Tenant-panel URL mapping for findings, run detail, and landing drill-down |
| `BaselineCompareCoverageBanner` | Banner visibility posture family, tone, headline, supporting message, next-action intent | Banner-specific show/hide threshold and local URL mapping |
| `BaselineCompareLanding` | Default-visible posture zone and next-action intent | Compare diagnostics, evidence-gap detail, duplicate-name detail, and existing `Compare now` action |
## Derived State Lifecycle
1. A covered tenant summary surface asks the resolver for the current tenant aggregate.
2. The resolver resolves or reuses one request-scoped aggregate for that tenant scope.
3. Covered surfaces read the same posture family, count family, and next-action intent from the shared aggregate.
4. If an in-request mutation or refresh path makes the current aggregate stale, the resolver uses explicit invalidation or fresh-resolution semantics.
5. The next request builds a new aggregate from current source truth.
## Migration Notes
- No schema migration is required.
- Existing `BaselineCompareStats` tests remain the low-level source-truth tests.
- If the implementation adds a new derived-state family, it must also extend the Spec 167 consumer-declaration contract and guard test.