## Summary - add a workspace-scoped baseline compare matrix page under baseline profiles - derive matrix tenant summaries, subject rows, cell states, freshness, and trust from existing snapshots, compare runs, and findings - add confirmation-gated `Compare assigned tenants` actions on the baseline detail and matrix surfaces without introducing a workspace umbrella run - preserve matrix navigation context into tenant compare and finding drilldowns and add centralized matrix badge semantics - include spec, plan, data model, contracts, quickstart, tasks, and focused feature/browser coverage for Spec 190 ## Verification - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Badges/BaselineCompareMatrixBadgesTest.php tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php tests/Feature/Baselines/BaselineComparePerformanceGuardTest.php tests/Feature/Filament/BaselineCompareMatrixPageTest.php tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php tests/Feature/Rbac/BaselineCompareMatrixAuthorizationTest.php tests/Feature/Guards/ActionSurfaceContractTest.php tests/Feature/Guards/NoAdHocStatusBadgesTest.php tests/Feature/Guards/NoDiagnosticWarningBadgesTest.php` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` - completed an integrated-browser smoke flow locally for matrix render, differ filter, finding drilldown round-trip, and `Compare assigned tenants` confirmation/action Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #221
272 lines
22 KiB
Markdown
272 lines
22 KiB
Markdown
# Implementation Plan: Workspace Baseline Compare Matrix V1
|
|
|
|
**Branch**: `190-baseline-compare-matrix` | **Date**: 2026-04-11 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/190-baseline-compare-matrix/spec.md`
|
|
**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/190-baseline-compare-matrix/spec.md`
|
|
|
|
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
|
|
|
|
## Summary
|
|
|
|
Add one workspace-scoped Filament page anchored on `BaselineProfile` that renders a tenant-by-subject compare matrix from the current effective baseline snapshot, visible assigned tenants, the latest relevant tenant `baseline_compare` runs, and compare-created findings. Reuse the existing tenant compare start path for `Compare assigned tenants`, reuse `CanonicalNavigationContext` for drilldown continuity, and keep V1 derived and read-only so no new persisted cross-tenant compare artifact or workspace umbrella run is introduced.
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: PHP 8.4.15
|
|
**Primary Dependencies**: Laravel 12, Filament v5, Livewire v4, Pest v4, existing `BaselineCompareService`, `BaselineSnapshotTruthResolver`, `BaselineCompareStats`, `RelatedNavigationResolver`, `CanonicalNavigationContext`, `BadgeCatalog`, and `UiEnforcement` patterns
|
|
**Storage**: PostgreSQL via existing `baseline_profiles`, `baseline_snapshots`, `baseline_snapshot_items`, `baseline_tenant_assignments`, `operation_runs`, and `findings` tables; no new persistence planned
|
|
**Testing**: Pest 4 feature and browser tests, Filament or Livewire page coverage, focused RBAC regressions, run through Laravel Sail
|
|
**Target Platform**: Laravel monolith web application in Sail locally and containerized Linux deployment in staging and production
|
|
**Project Type**: web application
|
|
**Performance Goals**: Keep matrix reads query-bounded over the visible tenant set and reference snapshot, avoid per-cell N+1 queries, keep render-time surfaces DB-only, and keep `Compare assigned tenants` enqueue-only by reusing tenant compare run starts
|
|
**Constraints**: No new persisted cross-tenant compare artifact; no new workspace umbrella `OperationRun`; hidden tenants must not leak by name or count; `Compare assigned tenants` must remain confirmation-gated and `simulation only`; matrix uses a narrow custom grid exception because a one-axis table cannot represent subject-by-tenant truth cleanly; no remote calls at render time
|
|
**Scale/Scope**: One workspace, one baseline profile at a time, all visible assigned tenants for that profile, all in-scope baseline subjects for the effective snapshot, one new page and view, one narrow derived matrix builder, additive baseline-detail header actions, and focused feature plus smoke-test coverage
|
|
|
|
## Constitution Check
|
|
|
|
*GATE: Passed before Phase 0 research. Re-checked after Phase 1 design and still passing.*
|
|
|
|
| Principle | Pre-Research | Post-Design | Notes |
|
|
|-----------|--------------|-------------|-------|
|
|
| Inventory-first / snapshots-second | PASS | PASS | The matrix reads current tenant evidence through existing compare runs and baseline snapshots; it does not redefine last-observed truth. |
|
|
| Read/write separation | PASS | PASS | The feature is read-only apart from compare start. `Compare assigned tenants` remains confirmation-gated and simulation-only, with existing run and audit semantics. |
|
|
| Graph contract path | N/A | N/A | No new Graph calls or contract-registry changes are required. |
|
|
| Deterministic capabilities | PASS | PASS | Existing capability constants `WORKSPACE_BASELINES_VIEW`, `WORKSPACE_BASELINES_MANAGE`, `TENANT_VIEW`, and `TENANT_FINDINGS_VIEW` remain canonical. |
|
|
| Workspace + tenant isolation | PASS | PASS | The matrix is workspace-scoped, its columns are limited to visible assigned tenants, and all drilldowns re-check tenant entitlement. |
|
|
| RBAC-UX authorization semantics | PASS | PASS | Non-members remain `404`; in-scope members missing required capability remain `403`; server-side authorization remains authoritative. |
|
|
| Run observability / Ops-UX | PASS | PASS | `Compare assigned tenants` fans out to existing tenant `baseline_compare` runs only. No new shadow run model or inline remote work is introduced. |
|
|
| Data minimization | PASS | PASS | The matrix reads existing compare and finding metadata only; no new payload copies or exported artifacts are stored. |
|
|
| Proportionality / no premature abstraction | PASS | PASS | The design adds one narrow matrix builder and one new page instead of a generalized compare framework or persisted portfolio layer. |
|
|
| Persisted truth / behavioral state | PASS | PASS | No new persistence or state family is added. Matrix states remain derived from existing truth. |
|
|
| UI semantics / few layers | PASS | PASS | Trust, stale, and ambiguity reuse existing compare semantics and do not create a new UI taxonomy. |
|
|
| Filament v5 / Livewire v4 compliance | PASS | PASS | The feature stays inside current Filament v5 and Livewire v4 patterns. |
|
|
| Provider registration location | PASS | PASS | No panel or provider changes are required. Provider registration remains in `bootstrap/providers.php`. |
|
|
| Global search hard rule | PASS | PASS | No new globally searchable resource is added. Existing baseline resources already satisfy current search constraints. |
|
|
| Destructive action safety | PASS | PASS | No destructive action is added. Existing destructive baseline actions remain unchanged and confirmation-gated. |
|
|
| Asset strategy | PASS | PASS | No new assets are planned. Existing deployment behavior for `filament:assets` remains unchanged. |
|
|
| Filament-native UI / Action Surface Contract | PASS WITH NARROW EXCEPTION | PASS WITH NARROW EXCEPTION | The matrix uses native Filament page structure and actions, but the core body is a custom two-dimensional grid because a one-axis table is insufficient. |
|
|
| Filament UX-001 | PASS WITH NARROW EXCEPTION | PASS WITH NARROW EXCEPTION | Sections, filters, legends, and empty states remain standard. The matrix body itself is the documented UX-001 exception. |
|
|
| Testing truth (TEST-TRUTH-001) | PASS | PASS | The test plan focuses on aggregation correctness, hidden-tenant safety, stale or ambiguous truth, compare-all fan-out, and drilldown continuity. |
|
|
|
|
## Phase 0 Research
|
|
|
|
Research outcomes are captured in `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/190-baseline-compare-matrix/research.md`.
|
|
|
|
Key decisions:
|
|
|
|
- Use `/admin/baseline-profiles/{record}/compare-matrix` as the canonical route and anchor the workflow under the existing baseline profile resource.
|
|
- Derive matrix cells and summaries from `BaselineSnapshotItem` plus the latest relevant `baseline_compare` run plus compare-created findings; findings alone are insufficient.
|
|
- Treat technical deviation state as primary and finding workflow state as secondary metadata so closed or risk-accepted findings do not hide real current drift.
|
|
- Reuse the existing stale-result threshold and current-reference comparison rather than inventing matrix-specific freshness rules.
|
|
- Reuse existing evidence-gap and trustworthiness semantics for ambiguity and low-confidence signals.
|
|
- Reuse `CanonicalNavigationContext` for matrix drilldown continuity instead of extending `PortfolioArrivalContext`.
|
|
- Implement `Compare assigned tenants` as visible-tenant fan-out over existing tenant compare starts without a workspace umbrella run.
|
|
- Validate primarily with focused feature tests plus one browser smoke test.
|
|
|
|
## Phase 1 Design
|
|
|
|
Design artifacts are created under `/Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/190-baseline-compare-matrix/`:
|
|
|
|
- `data-model.md`: existing source truths, derived matrix read models, and cell-state precedence
|
|
- `contracts/baseline-compare-matrix.logical.openapi.yaml`: internal logical contract for matrix reads and compare-all launch behavior
|
|
- `quickstart.md`: implementation sequence and focused verification workflow
|
|
|
|
Design decisions:
|
|
|
|
- The matrix stays fully derived and introduces no new table, artifact, or workspace-level run type.
|
|
- The new page is baseline-profile-scoped and reuses existing baseline detail, compare landing, finding detail, and operation-run viewer destinations.
|
|
- The core builder lives alongside existing baseline support code and returns plain derived arrays or value-oriented payloads rather than a new presenter framework.
|
|
- `Compare assigned tenants` iterates the visible assigned tenant set and reuses `BaselineCompareService::startCompare()` per tenant, reporting queued, already queued, and blocked results honestly.
|
|
- Cell state precedence is fixed so `not_compared`, `stale_result`, `ambiguous`, `missing`, and `differ` remain visibly distinct from `match`.
|
|
- Matrix drilldowns preserve a canonical return path by reusing `CanonicalNavigationContext` query propagation rather than inventing a new navigation token system.
|
|
|
|
## Project Structure
|
|
|
|
### Documentation (this feature)
|
|
|
|
```text
|
|
specs/190-baseline-compare-matrix/
|
|
├── spec.md
|
|
├── plan.md
|
|
├── research.md
|
|
├── data-model.md
|
|
├── quickstart.md
|
|
├── contracts/
|
|
│ └── baseline-compare-matrix.logical.openapi.yaml
|
|
├── checklists/
|
|
│ └── requirements.md
|
|
└── tasks.md
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
|
|
```text
|
|
apps/platform/
|
|
├── app/
|
|
│ ├── Filament/
|
|
│ │ ├── Pages/
|
|
│ │ │ ├── BaselineCompareLanding.php
|
|
│ │ │ └── BaselineCompareMatrix.php
|
|
│ │ └── Resources/
|
|
│ │ ├── BaselineProfileResource.php
|
|
│ │ ├── BaselineProfileResource/
|
|
│ │ │ └── Pages/ViewBaselineProfile.php
|
|
│ │ ├── FindingResource.php
|
|
│ │ └── OperationRunResource.php
|
|
│ ├── Models/
|
|
│ │ ├── BaselineProfile.php
|
|
│ │ ├── BaselineSnapshot.php
|
|
│ │ ├── BaselineSnapshotItem.php
|
|
│ │ ├── BaselineTenantAssignment.php
|
|
│ │ ├── Finding.php
|
|
│ │ └── OperationRun.php
|
|
│ ├── Services/
|
|
│ │ └── Baselines/BaselineCompareService.php
|
|
│ ├── Support/
|
|
│ │ ├── Baselines/
|
|
│ │ │ ├── BaselineCompareEvidenceGapDetails.php
|
|
│ │ │ ├── BaselineCompareMatrixBuilder.php
|
|
│ │ │ ├── BaselineCompareStats.php
|
|
│ │ │ ├── BaselineCompareSummaryAssessor.php
|
|
│ │ │ └── BaselineSnapshotTruthResolver.php
|
|
│ │ ├── Navigation/
|
|
│ │ │ ├── CanonicalNavigationContext.php
|
|
│ │ │ └── RelatedNavigationResolver.php
|
|
│ │ └── Rbac/
|
|
│ │ └── UiEnforcement.php
|
|
│ └── resources/views/filament/pages/
|
|
│ └── baseline-compare-matrix.blade.php
|
|
└── tests/
|
|
├── Browser/
|
|
│ └── Spec190BaselineCompareMatrixSmokeTest.php
|
|
├── Feature/
|
|
│ ├── Baselines/
|
|
│ │ ├── BaselineCompareMatrixBuilderTest.php
|
|
│ │ └── BaselineCompareMatrixCompareAllActionTest.php
|
|
│ ├── Filament/
|
|
│ │ └── BaselineCompareMatrixPageTest.php
|
|
│ └── Rbac/
|
|
│ └── BaselineCompareMatrixAuthorizationTest.php
|
|
└── Feature/Guards/
|
|
└── ActionSurfaceContractTest.php
|
|
```
|
|
|
|
**Structure Decision**: Keep the work inside the existing Laravel monolith under `apps/platform`. Add one new workspace page, one new view, one narrow baseline-support matrix builder, and additive changes to existing baseline detail and drilldown surfaces instead of creating a new domain package, new persistence layer, or new top-level workspace app.
|
|
|
|
## Implementation Strategy
|
|
|
|
### Phase A — Add The Workspace Entry Surface
|
|
|
|
**Goal**: Introduce the canonical matrix route and entry points without changing baseline ownership or navigation semantics.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| A.1 | `apps/platform/app/Filament/Pages/BaselineCompareMatrix.php` | Add the new workspace page class, scope it to one `BaselineProfile`, expose filter state, and declare the page contract plus action-surface intent |
|
|
| A.2 | `apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php` | Add the page view using Filament sections, summaries, legends, and one narrow custom grid body |
|
|
| A.3 | `apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php` | Add `Open compare matrix` and `Compare assigned tenants` header actions with confirmation and capability gating |
|
|
|
|
### Phase B — Build The Live Matrix Aggregation
|
|
|
|
**Goal**: Derive visible rows, columns, cells, summaries, freshness, and trust from existing truth only.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| B.1 | `apps/platform/app/Support/Baselines/BaselineCompareMatrixBuilder.php` | Add the narrow read-model builder that resolves reference snapshot, visible assigned tenants, subject rows, matrix cells, and summaries |
|
|
| B.2 | Existing baseline support classes such as `BaselineCompareStats.php`, `BaselineCompareEvidenceGapDetails.php`, and `BaselineCompareSummaryAssessor.php` | Reuse or extract helper logic for freshness, trust, evidence-gap interpretation, and no-result handling instead of duplicating semantics |
|
|
| B.3 | Existing models `BaselineTenantAssignment`, `Finding`, and `OperationRun` | Add or reuse scoped queries for visible tenant assignments, latest relevant compare runs per tenant, and compare-created findings keyed to the selected baseline profile |
|
|
|
|
### Phase C — Add Compare-All Fan-Out Over Existing Tenant Runs
|
|
|
|
**Goal**: Start compare for the visible assigned set without creating a workspace umbrella run.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| C.1 | `apps/platform/app/Services/Baselines/BaselineCompareService.php` | Add or extend a batch-start path that iterates visible assigned tenants and reuses `startCompare()` for each target |
|
|
| C.2 | `apps/platform/app/Filament/Pages/BaselineCompareMatrix.php` | Surface `Compare assigned tenants`, call the batch-start path, render queued, already queued, and blocked outcomes honestly, and preserve visible-disabled helper text for in-scope users missing manage capability |
|
|
| C.3 | Existing operation feedback helpers such as `OperationUxPresenter` and `OpsUxBrowserEvents` | Reuse current queued feedback and run-link behavior; do not invent matrix-specific run notifications |
|
|
|
|
### Phase D — Bind Filters, Drilldowns, And Degraded States
|
|
|
|
**Goal**: Make the matrix operator-scanable, trustworthy, and continuous with existing follow-up surfaces.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| D.1 | `apps/platform/app/Filament/Pages/BaselineCompareMatrix.php` | Add policy-type, state, severity, tenant-sort, subject-sort, and focused-subject state |
|
|
| D.2 | `apps/platform/app/Support/Navigation/CanonicalNavigationContext.php` and related call sites | Reuse canonical navigation context so cell, tenant, finding, and run drilldowns preserve a return path to the matrix |
|
|
| D.3 | Existing drilldown destinations such as `BaselineCompareLanding.php`, `FindingResource.php`, and `OperationRunResource.php` | Accept bounded matrix source context and render a meaningful back link or source hint without adding a new navigation system |
|
|
| D.4 | Matrix view and builder | Render explicit empty, blocked, partial, stale, ambiguous, and no-result states without collapsing them into matches |
|
|
|
|
### Phase E — Regression Protection And Hardening
|
|
|
|
**Goal**: Prove business truth, hidden-tenant safety, and action-surface compliance.
|
|
|
|
| Step | File | Change |
|
|
|------|------|--------|
|
|
| E.1 | `apps/platform/tests/Feature/Baselines/BaselineCompareMatrixBuilderTest.php` | Cover row and cell derivation, stale or no-result logic, ambiguity, and visible-set-only summaries |
|
|
| E.2 | `apps/platform/tests/Feature/Baselines/BaselineCompareMatrixCompareAllActionTest.php` | Cover compare-all fan-out, per-tenant queued or blocked outcomes, and no workspace umbrella run creation |
|
|
| E.3 | `apps/platform/tests/Feature/Filament/BaselineCompareMatrixPageTest.php` | Cover page mount, filters, subject focus, degraded states, and drilldown link presence |
|
|
| E.4 | `apps/platform/tests/Feature/Rbac/BaselineCompareMatrixAuthorizationTest.php` | Cover non-member `404`, member-without-capability `403`, visible-set filtering, and allowed access |
|
|
| E.5 | `apps/platform/tests/Browser/Spec190BaselineCompareMatrixSmokeTest.php` | Add one smoke test for the rendered matrix, one core filter interaction, and one drilldown or compare-all affordance |
|
|
| E.6 | `apps/platform/tests/Feature/Guards/ActionSurfaceContractTest.php` | Keep the new page and baseline detail actions aligned with action-surface rules |
|
|
|
|
## Key Design Decisions
|
|
|
|
### D-001 — Keep the matrix fully derived
|
|
|
|
The page must read from existing baseline, compare, finding, and run truth only. No `CrossTenantCompare`, no stored matrix snapshot, and no new reporting artifact are introduced in V1.
|
|
|
|
### D-002 — Keep baseline profile as the visible standard
|
|
|
|
The matrix route lives under the selected baseline profile because the operator question is not generic cross-tenant compare; it is reference-to-many compare against one workspace-owned standard.
|
|
|
|
### D-003 — Reuse tenant compare runs only
|
|
|
|
`Compare assigned tenants` fans out across the visible assigned tenant set through existing tenant compare starts. Aggregate progress on the matrix page derives from those tenant runs instead of a workspace batch run.
|
|
|
|
### D-004 — Technical drift comes before workflow posture
|
|
|
|
Cells and counts reflect technical compare truth first. Finding workflow state, risk acceptance, or acknowledgement remain secondary metadata and do not suppress a current `differ` or `missing` state.
|
|
|
|
### D-005 — Reuse canonical navigation context
|
|
|
|
Drilldown continuity uses existing canonical navigation patterns rather than extending portfolio-triage tokens or adding a new navigation framework.
|
|
|
|
### D-006 — Accept one narrow matrix-grid UI exception
|
|
|
|
The matrix body is the only major UI exception because a standard table cannot represent subject-by-tenant truth. Everything around the grid remains Filament-native: actions, sections, legends, empty states, and badges.
|
|
|
|
## Risk Assessment
|
|
|
|
| Risk | Impact | Likelihood | Mitigation |
|
|
|------|--------|------------|------------|
|
|
| Hidden-tenant leakage through summaries or totals | High | Medium | Build every column and summary from the visible tenant set only; add explicit negative RBAC tests. |
|
|
| Findings-only aggregation understates current drift | High | Medium | Derive technical state from latest compare run plus findings; treat finding workflow as secondary metadata. |
|
|
| Stale or no-result truth looks calm | High | Medium | Reuse stale-result semantics, explicit `not_compared`, and blocked-state rendering; test all degraded cases. |
|
|
| Matrix render becomes query-heavy | High | Medium | Build one narrow aggregation layer that batches tenant runs, findings, and snapshot items instead of resolving per cell. |
|
|
| Compare-all introduces a shadow batch model | Medium | Medium | Reuse `BaselineCompareService::startCompare()` per tenant and return a simple launch summary only. |
|
|
| The page grows into a generalized standardization platform | Medium | Medium | Keep route, copy, and design anchored on one baseline profile, one reference snapshot, and read-only workflow scope. |
|
|
|
|
## Test Strategy
|
|
|
|
- Add focused feature coverage for matrix aggregation, cell precedence, tenant and subject summaries, stale or no-result truth, and visible-set-only counts.
|
|
- Add action coverage for compare-all fan-out, per-tenant queued or blocked outcomes, reuse of the existing tenant compare run path, service-owned run transitions, canonical summary-count invariants, and notification-surface limits.
|
|
- Add RBAC coverage for workspace membership, capability denial, partial tenant visibility, visible-disabled helper-text states for in-scope users missing manage capability, and drilldown authorization semantics.
|
|
- Add query-shape regression coverage so wide tenant/subject matrices stay batched and avoid per-cell N+1 resolution.
|
|
- Add one browser smoke test for matrix rendering plus a core filter or drilldown interaction.
|
|
- Keep action-surface and existing baseline compare suites green so the new page does not regress current baseline workflows.
|
|
- Run the minimum focused Sail pack plus `pint --dirty --format agent` before implementation sign-off.
|
|
|
|
## Complexity Tracking
|
|
|
|
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
|
|-----------|------------|-------------------------------------|
|
|
| Two-dimensional matrix grid on a Filament page | The operator must scan subjects across multiple visible tenant columns in one surface | A standard Filament table or tenant-by-tenant list cannot represent both axes without destroying the core workflow question |
|
|
|
|
## Proportionality Review
|
|
|
|
- **Current operator problem**: Operators can already define baselines and compare one tenant at a time, but they still cannot answer cross-tenant baseline deviation questions without manual aggregation.
|
|
- **Existing structure is insufficient because**: Existing tenant compare and finding surfaces are tenant-first. They do not present one visible-set baseline view across all assigned tenants.
|
|
- **Narrowest correct implementation**: Add one baseline-scoped matrix page, one narrow derived matrix builder, one centralized badge adapter that reuses canonical compare truth, and one compare-all fan-out path over existing tenant compare starts.
|
|
- **Ownership cost created**: One new page, one view, one derived aggregation helper, one centralized badge adapter, additive baseline-detail actions, and focused feature plus browser regression coverage.
|
|
- **Alternative intentionally rejected**: A persisted cross-tenant compare artifact, a generalized compare engine, or a workspace umbrella run were rejected because they add durable complexity beyond V1's read-only portfolio visibility goal.
|
|
- **Release truth**: Current-release truth. The feature closes a present operator workflow gap using already-shipped baseline, finding, and run foundations.
|