15 KiB
Implementation Plan: Cross-Tenant Compare Preview and Promotion Preflight
Branch: 043-cross-tenant-compare-and-promotion | Date: 2026-04-27 | Spec: spec.md
Input: Feature specification from 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\BaselineCompareLandingApp\Filament\Pages\BaselineCompareMatrixApp\Filament\Resources\TenantResourceApp\Filament\Resources\TenantResource\Pages\ListTenantsApp\Services\Baselines\BaselineCompareServiceApp\Support\Baselines\BaselineCompareMatrixBuilderApp\Support\Baselines\Compare\CompareStrategyRegistryApp\Services\PortfolioTriage\TenantTriageReviewServiceApp\Services\Audit\WorkspaceAuditLoggerApp\Support\Audit\AuditActionIdApp\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, andblocked 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
404for inaccessible tenants. - RBAC-UX plane separation: PASS. This slice lives only in
/admin; no/systemor 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
UnitplusFeature; 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:
Featurefor the compare page, launch context, auth, and audit;Unitfor 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.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Support/PortfolioCompare/CrossTenantPromotionPreflightTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/PortfolioCompare/CrossTenantComparePageTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/PortfolioCompare/CrossTenantCompareAuthorizationTest.phpexport PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/PortfolioCompare/CrossTenantPromotionPreflightAuditTest.phpexport 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-featurefor contained lane drift,reject-or-splitfor 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)
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)
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
- Reuse current compare, navigation, capability, and audit seams.
- Deliver the canonical compare preview.
- Add the read-only promotion preflight on top of the same page and services.
- Add launch/return continuity from portfolio-triage and tenant-registry context.
- Finish with narrow validation and formatting.
Team Strategy
- Settle the preview/preflight contracts first.
- Parallelize unit tests for preview/preflight rules and feature tests for page/auth behavior.
- Serialize merges around the canonical compare page and the shared
PortfolioCompareservice namespace so the page contract does not drift.