## 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
213 lines
9.5 KiB
Markdown
213 lines
9.5 KiB
Markdown
# 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 tenant’s 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. |