TenantAtlas/specs/043-cross-tenant-compare-and-promotion/plan.md
Ahmed Darrazi 6383f205a1
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m3s
chore: commit all changes (automated) 2026-04-27T21:17:40Z
2026-04-27 23:17:40 +02:00

211 lines
15 KiB
Markdown

# Implementation Plan: Cross-Tenant Compare Preview and Promotion Preflight
**Branch**: `043-cross-tenant-compare-and-promotion` | **Date**: 2026-04-27 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from [spec.md](spec.md)
## Summary
Refresh Spec 043 into a narrow, implementation-ready workflow that adds one canonical workspace-context compare page under `/admin`, one reusable compare preview builder, and one read-only promotion preflight action. The slice reuses existing baseline compare subject identity, portfolio-triage context continuity, capability resolvers, and workspace audit logging. It deliberately stops before actual promotion execution, queueing, or persisted promotion drafts.
Filament remains on Livewire v4, no panel-provider registration changes are required (`bootstrap/providers.php` remains the authoritative provider registration location), no globally searchable compare resource is added, and no new panel asset bundle is expected.
## Technical Context
**Language/Version**: PHP 8.4, Laravel 12
**Primary Dependencies**: Filament v5, Livewire v4, Pest v4, existing baseline compare services, portfolio-triage seams, audit services, and capability resolvers
**Storage**: PostgreSQL via existing inventory, policy-version, and audit tables; no new compare or promotion table
**Testing**: Pest v4 `Unit` and `Feature` coverage only
**Validation Lanes**: fast-feedback, confidence
**Target Platform**: Laravel monolith in `apps/platform`, admin panel only (`/admin`)
**Project Type**: Web application (Laravel monolith with Filament pages)
**Performance Goals**: compare preview and promotion preflight stay synchronous and derived from existing persisted truth; no background execution path in v1
**Constraints**: no target mutation, no `OperationRun`, no queue, no new persisted draft, no cross-workspace compare, no raw payload view by default
**Scale/Scope**: 2 tenant selectors, 1 canonical compare page, 1 preflight action, 1 launch/return continuity path, focused reuse of existing compare builders
## UI / Surface Guardrail Plan
- **Guardrail scope**: one new canonical compare page plus one launch action from existing tenant-registry/portfolio context
- **Native vs custom classification summary**: native Filament page with shared compare/audit/navigation primitives
- **Shared-family relevance**: canonical admin pages, compare drill-down patterns, launch actions, audit-backed modal/action copy
- **State layers in scope**: page, query state
- **Audience modes in scope**: operator-MSP only
- **Decision/diagnostic/raw hierarchy plan**: decision-first compare summary, diagnostics second, raw evidence stays on existing tenant/baseline surfaces
- **Raw/support gating plan**: no new raw/support surface; keep payload proof behind existing pages
- **One-primary-action / duplicate-truth control**: the compare page keeps one dominant next action, `Generate promotion preflight`; drill-down and return actions stay secondary
- **Launch default**: the tenant-registry launch action prefills the launched tenant as `target tenant`; the operator chooses the source tenant explicitly
- **Handling modes by drift class or surface**: review-mandatory; any actual promotion execution or queue path is exception-required and out of scope
- **Repository-signal treatment**: review-mandatory
- **Special surface test profiles**: standard-native-filament
- **Required tests or manual smoke**: functional-core, state-contract
- **Exception path and spread control**: none
- **Active feature PR close-out entry**: Guardrail
## Shared Pattern & System Fit
- **Cross-cutting feature marker**: yes
- **Systems touched**:
- `App\Filament\Pages\BaselineCompareLanding`
- `App\Filament\Pages\BaselineCompareMatrix`
- `App\Filament\Resources\TenantResource`
- `App\Filament\Resources\TenantResource\Pages\ListTenants`
- `App\Services\Baselines\BaselineCompareService`
- `App\Support\Baselines\BaselineCompareMatrixBuilder`
- `App\Support\Baselines\Compare\CompareStrategyRegistry`
- `App\Services\PortfolioTriage\TenantTriageReviewService`
- `App\Services\Audit\WorkspaceAuditLogger`
- `App\Support\Audit\AuditActionId`
- `App\Support\Navigation\CanonicalNavigationContext`
- **Shared abstractions reused**: capability resolvers, baseline compare strategy selection, canonical navigation context, existing audit recorder/logger path, and tenant-registry return-state conventions
- **New abstraction introduced? why?**: one narrow compare preview builder and one narrow promotion preflight service, because no existing service accepts source+target tenant scope and computes promotion readiness without execution
- **Why the existing abstraction was sufficient or insufficient**: tenant-level baseline compare is sufficient for subject identity, evidence posture, and drill-down semantics, but insufficient for dual-tenant scope and promotion-readiness reasoning
- **Bounded deviation / spread control**: no local compare sidecars on tenant pages; future callers must route through the canonical compare page and its services
## OperationRun UX Impact
- **Touches OperationRun start/completion/link UX?**: no
- **Central contract reused**: `N/A`
- **Delegated UX behaviors**: `N/A`
- **Surface-owned behavior kept local**: compare preview and preflight remain synchronous and read-only
- **Queued DB-notification policy**: `N/A`
- **Terminal notification path**: `N/A`
- **Exception path**: none
## Provider Boundary & Portability Fit
- **Shared provider/platform boundary touched?**: yes
- **Provider-owned seams**: Microsoft-first inventory subject identity and policy-type mapping remain inside existing baseline compare strategy selection and inventory data
- **Platform-core seams**: source/target tenant scope, compare preview contract, promotion preflight contract, operator-facing readiness vocabulary
- **Neutral platform terms / contracts preserved**: `source tenant`, `target tenant`, `governed subject`, `compare preview`, `promotion preflight`, and `blocked reason`
- **Retained provider-specific semantics and why**: existing policy-type and inventory semantics remain Microsoft-first because this repo still has one real provider domain; the compare page should not invent fake provider-neutral mapping logic above that seam
- **Bounded extraction or follow-up path**: follow-up-spec only if later provider domains become current-release truth
## Constitution Check
*GATE: Must pass before implementation preparation continues.*
- Inventory-first: PASS. Compare preview and preflight derive from existing inventory and policy-version truth rather than a new compare snapshot.
- Read/write separation: PASS. This slice stays read-only; no write execution is introduced.
- Graph contract path: PASS. No new Graph endpoint or direct provider call is added.
- Deterministic capabilities: PASS. Reuse existing capability registries such as `Capabilities::TENANT_VIEW`, `Capabilities::WORKSPACE_BASELINES_VIEW`, `Capabilities::WORKSPACE_BASELINES_MANAGE`, and existing tenant sync/manage seams.
- Workspace and tenant isolation: PASS. The compare page must resolve workspace membership first and source/target entitlement second, with `404` for inaccessible tenants.
- RBAC-UX plane separation: PASS. This slice lives only in `/admin`; no `/system` or cross-plane route is introduced.
- Destructive action discipline: PASS by non-use. The slice contains no destructive action.
- Global search: PASS. No new Resource or Global Search result is introduced.
- OperationRun / Ops-UX: PASS by non-use. Actual promotion execution is deferred.
- Data minimization: PASS. The compare page summarizes derived readiness and blocks; raw payloads stay on existing tenant/baseline pages.
- Test governance: PASS. Proof stays in `Unit` plus `Feature`; no browser or heavy-governance expansion is planned.
- Proportionality / no premature abstraction: PASS. One preview builder and one preflight service are justified by the dual-tenant workflow; no new persistence or framework layer is added.
- Persisted truth: PASS. No new compare or promotion table.
- Behavioral state: PASS. Readiness and blocked reasons remain derived, not persisted.
- Shared pattern first / UI semantics / Filament-native UI: PASS. Existing compare, navigation, and audit paths are extended rather than replaced.
- Provider boundary: PASS. Microsoft-shaped subject matching stays in existing strategy seams; the page contract stays platform-neutral.
- Filament/Laravel panel safety: PASS. Filament v5 remains on Livewire v4, no provider registration change beyond `bootstrap/providers.php`, and no new assets are planned.
**Gate evaluation**: PASS.
## Test Governance Check
- **Test purpose / classification by changed surface**: `Feature` for the compare page, launch context, auth, and audit; `Unit` for compare preview matching and promotion-preflight classification
- **Affected validation lanes**: fast-feedback, confidence
- **Why this lane mix is the narrowest sufficient proof**: feature tests prove the Filament page and launch path while unit tests keep preview/preflight rules cheap and isolated. Browser or heavy-governance coverage is not required for the first read-only slice.
- **Narrowest proving command(s)**:
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/PortfolioCompare/CrossTenantComparePreviewBuilderTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/PortfolioCompare/CrossTenantPromotionPreflightTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/PortfolioCompare/CrossTenantComparePageTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/PortfolioCompare/CrossTenantCompareAuthorizationTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/PortfolioCompare/CrossTenantPromotionPreflightAuditTest.php`
- `export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/PortfolioCompare/CrossTenantCompareLaunchContextTest.php`
- **Fixture / helper / factory / seed / context cost risks**: reuse existing inventory, baseline compare, tenant registry, and portfolio-triage fixtures; avoid browser setup, queue fixtures, or seeded promotion history
- **Expensive defaults or shared helper growth introduced?**: no
- **Heavy-family additions, promotions, or visibility changes**: none
- **Surface-class relief / special coverage rule**: standard-native-filament
- **Closing validation and reviewer handoff**: rerun the six focused commands above and confirm the slice remains read-only, deny-as-not-found-safe, and grounded on existing compare + portfolio seams
- **Budget / baseline / trend follow-up**: none expected
- **Review-stop questions**: lane fit, hidden fixture growth, accidental write execution, accidental queue/runtime scope
- **Escalation path**: `document-in-feature` for contained lane drift, `reject-or-split` for any attempt to add execution scope
- **Active feature PR close-out entry**: Guardrail
- **Why no dedicated follow-up spec is needed**: test upkeep remains feature-local; only actual promotion execution or multi-provider compare would warrant a separate follow-up spec
## Project Structure
### Documentation (this feature)
```text
specs/043-cross-tenant-compare-and-promotion/
├── checklists/
│ └── requirements.md
├── spec.md
├── plan.md
└── tasks.md
```
This refresh intentionally limits itself to the core preparation package plus `checklists/requirements.md`. No additional research/data-model/contracts artifact is required to make the narrowed slice implementation-ready.
### Source Code (repository root)
```text
apps/platform/
├── app/
│ ├── Filament/Pages/
│ │ ├── BaselineCompareLanding.php
│ │ ├── BaselineCompareMatrix.php
│ │ └── [new canonical compare page]
│ ├── Filament/Resources/TenantResource.php
│ ├── Filament/Resources/TenantResource/Pages/ListTenants.php
│ ├── Models/
│ │ ├── InventoryItem.php
│ │ └── PolicyVersion.php
│ ├── Services/Audit/
│ │ └── WorkspaceAuditLogger.php
│ ├── Services/Baselines/
│ │ └── BaselineCompareService.php
│ ├── Services/PortfolioTriage/
│ │ └── TenantTriageReviewService.php
│ ├── Support/Audit/AuditActionId.php
│ ├── Support/Baselines/
│ │ ├── BaselineCompareMatrixBuilder.php
│ │ └── Compare/CompareStrategyRegistry.php
│ └── Support/PortfolioCompare/ or Services/PortfolioCompare/
└── tests/
├── Feature/PortfolioCompare/
└── Unit/Support/PortfolioCompare/
```
**Structure Decision**: keep implementation inside `apps/platform`, reuse existing compare and portfolio seams, and introduce at most one small `PortfolioCompare` support/service namespace for the new dual-tenant preview/preflight logic.
## Complexity Tracking
| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| New compare preview builder | dual-tenant compare needs one place to translate existing inventory/baseline truth into a canonical preview contract | page-local mapping would duplicate compare logic and drift from existing baseline compare seams |
| New promotion preflight service | readiness reasoning must stay read-only and auditable before any execution path exists | bolting readiness rules into the page would make later reuse and testing brittle |
## Proportionality Review
- **Current operator problem**: portfolio operators still lack one bounded surface that answers whether a target tenant can follow a source tenant.
- **Existing structure is insufficient because**: existing baseline compare is tenant-vs-reference, not tenant-vs-tenant, and portfolio triage does not compute promotion readiness.
- **Narrowest correct implementation**: one canonical page plus one preview builder and one preflight service, no new table, no execution path.
- **Ownership cost created**: maintain a small preview/preflight contract and a focused test family.
- **Alternative intentionally rejected**: actual promotion execution, persisted promotion drafts, and local compare sidecars were rejected as premature.
- **Release truth**: current-release gap, not speculative platform work.
## Implementation Strategy
### Suggested MVP Scope
MVP = **US1 + US2 together**. A compare page without a promotion preflight leaves the core decision incomplete, and a preflight without a canonical compare page has no trustworthy operator context.
### Incremental Delivery
1. Reuse current compare, navigation, capability, and audit seams.
2. Deliver the canonical compare preview.
3. Add the read-only promotion preflight on top of the same page and services.
4. Add launch/return continuity from portfolio-triage and tenant-registry context.
5. Finish with narrow validation and formatting.
### Team Strategy
1. Settle the preview/preflight contracts first.
2. Parallelize unit tests for preview/preflight rules and feature tests for page/auth behavior.
3. Serialize merges around the canonical compare page and the shared `PortfolioCompare` service namespace so the page contract does not drift.