## 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
22 KiB
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-matrixas the canonical route and anchor the workflow under the existing baseline profile resource. - Derive matrix cells and summaries from
BaselineSnapshotItemplus the latest relevantbaseline_comparerun 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
CanonicalNavigationContextfor matrix drilldown continuity instead of extendingPortfolioArrivalContext. - Implement
Compare assigned tenantsas 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 precedencecontracts/baseline-compare-matrix.logical.openapi.yaml: internal logical contract for matrix reads and compare-all launch behaviorquickstart.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 tenantsiterates the visible assigned tenant set and reusesBaselineCompareService::startCompare()per tenant, reporting queued, already queued, and blocked results honestly.- Cell state precedence is fixed so
not_compared,stale_result,ambiguous,missing, anddifferremain visibly distinct frommatch. - Matrix drilldowns preserve a canonical return path by reusing
CanonicalNavigationContextquery propagation rather than inventing a new navigation token system.
Project Structure
Documentation (this feature)
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)
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 agentbefore 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.