TenantAtlas/specs/043-cross-tenant-compare-and-promotion/spec.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

29 KiB

Feature Specification: Cross-Tenant Compare Preview and Promotion Preflight

Feature Branch: 043-cross-tenant-compare-and-promotion
Created: 2026-01-07
Updated: 2026-04-27
Status: Ready for implementation
Input: Refresh existing Spec 043 against docs/product/spec-candidates.md, docs/product/implementation-ledger.md, and docs/product/roadmap.md so the feature becomes a narrow, implementation-ready slice instead of a broad future ambition.

Spec Candidate Check (mandatory - SPEC-GATE-001)

  • Problem: TenantPilot now has portfolio visibility, triage continuity, and strong tenant-level baseline compare surfaces, but operators still lack one canonical workspace-level path to compare a source tenant to a target tenant and prepare a safe promotion decision.
  • Today's failure: Operators can see that tenants differ, but they still reconstruct cross-tenant decisions manually across tenant registry, baseline compare, and tenant detail surfaces. Promotion remains a roadmap phrase, not a bounded product workflow.
  • User-visible improvement: An authorized workspace operator can select a source and target tenant, review a structured compare preview of governed subjects, and generate a read-only promotion preflight that shows what is ready, blocked, or requires manual mapping before any write path exists.
  • Smallest enterprise-capable version: One canonical /admin compare surface, one compare preview builder, one read-only promotion preflight action, deep links back to existing tenant and baseline compare surfaces, and bounded audit metadata for preflight entry points. No actual promotion execution ships in this slice.
  • Explicit non-goals: No cutover, no write execution, no queue or OperationRun, no automatic target remapping of groups/tags/named locations, no cross-workspace compare, no customer-facing compare workspace, no provider marketplace, and no new persisted promotion draft entity.
  • Permanent complexity imported: One canonical compare page, one narrow compare scope contract, one preview/preflight builder pair, one small audit metadata shape, and focused unit plus feature coverage.
  • Why now: The implementation ledger explicitly identifies cross-tenant compare and promotion as one of the remaining real product gaps. It is the missing bridge between portfolio visibility and portfolio action.
  • Why not local: A local compare action on one tenant page would duplicate entitlement, matching, audit, and promotion-readiness logic and would not create a reusable, canonical workspace workflow.
  • Approval class: Workflow Compression
  • Red flags triggered: New page + new compare/preflight service pair. Defense: the slice stays read-only, introduces no new table, reuses existing baseline compare and portfolio triage seams, and defers actual execution.
  • Score: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 1 | Produktnaehe: 1 | Wiederverwendung: 2 | Gesamt: 10/12
  • Decision: approve

Spec Scope Fields (mandatory)

  • Scope: canonical-view
  • Primary Routes:
    • new canonical admin compare page under /admin for cross-tenant compare preview and promotion preflight
    • existing /admin/tenants portfolio/registry surfaces as launch and return context
    • existing tenant detail and baseline compare pages as secondary drill-down targets rather than duplicated local detail panes
  • Data Ownership:
    • compare preview and promotion preflight remain derived from existing tenant-owned inventory, policy-version, and baseline-compare truth
    • no new compare snapshot, promotion draft, or mapping table is introduced in v1
    • audit remains on the existing workspace audit log only
  • RBAC:
    • non-members or actors outside workspace scope receive 404
    • launch-action visibility requires established workspace context, Capabilities::WORKSPACE_BASELINES_VIEW on the workspace, and Capabilities::TENANT_VIEW on the launched tenant
    • opening the compare page requires established workspace context and Capabilities::WORKSPACE_BASELINES_VIEW on the workspace
    • loading preview data requires Capabilities::TENANT_VIEW on both source and target tenants
    • executing promotion preflight requires the preview permissions plus Capabilities::WORKSPACE_BASELINES_MANAGE on the workspace
    • for established members who can view compare but lack Capabilities::WORKSPACE_BASELINES_MANAGE, the preflight action remains visible but disabled with explicit permission help text; server-side attempts still return 403
    • the implementation must stay on existing capability registries instead of raw strings and must not introduce a new promotion capability family for this slice

For canonical-view specs, the spec MUST define:

  • Default filter behavior when tenant-context is active: if launched from the tenant registry or portfolio-triage context, prefill the launched tenant as the target tenant, leave the source tenant intentionally user-selected, and preserve a return context token.
  • Explicit entitlement checks preventing cross-tenant leakage: the compare surface must validate workspace membership first, then validate both source and target tenant entitlement before any preview data loads. Any inaccessible tenant input is treated as not found.

Cross-Cutting / Shared Pattern Reuse (mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write N/A - no shared interaction family touched)

  • Cross-cutting feature?: yes
  • Interaction class(es): navigation entry points, compare/drill-down actions, audit metadata, and canonical workspace-context pages
  • Systems touched: ListTenants, portfolio-triage state, CanonicalNavigationContext, BaselineCompareLanding, BaselineCompareMatrix, BaselineCompareService, CompareStrategyRegistry, WorkspaceAuditLogger, and AuditActionId
  • Existing pattern(s) to extend: canonical /admin workspace-context pages, baseline compare preview patterns, portfolio-triage return-state patterns, and existing workspace audit metadata patterns
  • Shared contract / presenter / builder / renderer to reuse: CanonicalNavigationContext, ActionSurfaceDeclaration, BaselineCompareService, BaselineCompareMatrixBuilder, CompareStrategyRegistry, TenantTriageReviewService, and WorkspaceAuditLogger
  • Why the existing shared path is sufficient or insufficient: existing tenant-level baseline compare surfaces already solve stable subject matching, result framing, and drill-down semantics, but they are insufficient for cross-tenant compare because they do not accept dual-tenant scope or produce a promotion-readiness preflight.
  • Allowed deviation and why: none. The new surface should extend current compare and navigation patterns, not invent a parallel compare UX family.
  • Consistency impact: source tenant, target tenant, compare preview, promotion preflight, blocked reason, and ready/manual mapping language must stay consistent across page copy, modal copy, audit prose, and deep links.
  • Review focus: reviewers must block new local compare widgets or tenant-specific preflight sidecars that bypass the canonical compare page or its shared preview/preflight services.
  • Touches OperationRun start/completion/link UX?: no
  • Shared OperationRun UX contract/layer reused: N/A
  • Delegated start/completion UX behaviors: N/A
  • Local surface-owned behavior that remains: compare preview and promotion preflight stay synchronous and read-only in v1
  • Queued DB-notification policy: N/A
  • Terminal notification path: N/A
  • Exception required?: none

Provider Boundary / Platform Core Check (mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write N/A - no shared provider/platform boundary touched)

  • Shared provider/platform boundary touched?: yes
  • Boundary classification: mixed
  • Seams affected: compare subject identity, compare strategy reuse, promotion preflight reason vocabulary, and operator-facing compare terminology
  • Neutral platform terms preserved or introduced: source tenant, target tenant, governed subject, compare preview, promotion preflight, mapping gap, and blocked reason
  • Provider-specific semantics retained and why: Microsoft-first policy-type and inventory semantics remain inside existing compare strategy and inventory seams because the repo currently has one real provider domain. They should not leak deeper into the page contract than necessary.
  • Why this does not deepen provider coupling accidentally: the page and services stay anchored on existing compare registries and inventory identifiers instead of inventing Microsoft-specific page contracts or raw Graph payload handling.
  • Follow-up path: future multi-provider compare remains a separate follow-up spec if it ever becomes current-release truth.

UI / Surface Guardrail Impact (mandatory when operator-facing surfaces are changed; otherwise write N/A)

Surface / Change Operator-facing surface change? Native vs Custom Shared-Family Relevance State Layers Touched Exception Needed? Low-Impact / N/A Note
Canonical cross-tenant compare page yes Native Filament page plus shared compare primitives compare preview, navigation, audit-backed preflight action page, query state, compare summary, modal/action state no Reuses baseline compare language and drill-down patterns instead of a custom standalone shell
Tenant registry / portfolio launch action yes Native Filament action navigation entry point, contextual launch table state, query/deep-link state no Extends existing portfolio-triage return-state handling
Actual promotion execution surface no N/A none none no N/A - explicitly deferred

Decision-First Surface Role (mandatory when operator-facing surfaces are changed)

Surface Decision Role Human-in-the-loop Moment Immediately Visible for First Decision On-Demand Detail / Evidence Why This Is Primary or Why Not Workflow Alignment Attention-load Reduction
Canonical cross-tenant compare page Primary Decision Surface Operator decides whether the target tenant is ready for promotion planning or still blocked by scope and mapping gaps source/target summary, ready/blocked/manual counts, top blockers, and next action tenant drill-down, baseline compare drill-down, subject-level diagnostics Primary because it is the first canonical workspace place where cross-tenant action becomes decidable Moves from portfolio triage into compare and preflight without manual reconstruction Replaces cross-page mental diffing with one bounded decision surface
Tenant registry / portfolio launch action Secondary Context Operator chooses when to leave the tenant registry for compare current tenant context and preserved return state compare details live on the compare page Secondary because it launches the decision surface rather than hosting it Keeps portfolio review flow intact Reduces repeated tenant re-selection and filter loss

Audience-Aware Disclosure (mandatory when operator-facing surfaces are changed)

Surface Audience Modes In Scope Decision-First Default-Visible Content Operator Diagnostics Support / Raw Evidence One Dominant Next Action Hidden / Gated By Default Duplicate-Truth Prevention
Canonical cross-tenant compare page operator-MSP source/target summary, compare counts, preflight readiness summary, top blocked reasons subject-level mapping gaps and deep links to tenant-specific evidence raw payloads remain on existing tenant/baseline pages, not this surface Generate promotion preflight raw JSON, provider IDs, and low-level evidence stay behind existing detail pages compare page states the decision truth once; drill-down pages add proof rather than rephrasing the same blocker
Tenant registry / portfolio launch action operator-MSP current tenant context and compare launch intent return-state token only none Compare tenants any future write action remains absent launch action does not duplicate compare summaries on the registry row

UI/UX Surface Classification (mandatory when operator-facing surfaces are changed)

Surface Action Surface Class Surface Type Likely Next Operator Action Primary Inspect/Open Model Row Click Secondary Actions Placement Destructive Actions Placement Canonical Collection Route Canonical Detail Route Scope Signals Canonical Noun Critical Truth Visible by Default Exception Type / Justification
Canonical cross-tenant compare page Utility / Workspace Decision Draft apply analysis Generate promotion preflight or open drill-down evidence explicit selectors plus focused compare/preflight panels forbidden drill-down links and secondary navigation stay below the summary/preflight sections none in v1 new canonical /admin compare route same page with shareable query state workspace context plus source/target tenant chips Cross-tenant compare whether the target is ready, blocked, or needs manual mapping none
Tenant registry / portfolio launch action List / Table / Launch Context Launch context support Open compare with current tenant prefilled explicit action from tenant list or triage context preserved existing row behavior compare entry is a safe secondary action none /admin/tenants compare route current workspace and tenant Tenant registry why the action launches compare, not promotion existing tenant registry action hierarchy remains valid

Operator Surface Contract (mandatory when operator-facing surfaces are changed)

Surface Primary Persona Decision / Operator Action Supported Surface Type Primary Operator Question Default-visible Information Diagnostics-only Information Status Dimensions Used Mutation Scope Primary Actions Dangerous Actions
Canonical cross-tenant compare page Workspace operator / MSP operator Decide whether a target tenant is ready for a later promotion workflow Canonical decision page Can this target tenant safely follow the selected source tenant for the chosen governed subjects? source/target summary, compare counts, blocked reasons, ready/manual counts, and next action subject-level mappings, stale evidence signals, and deep links to existing tenant compare/detail surfaces compare state, readiness, mapping confidence, evidence freshness TenantPilot only in v1 Generate promotion preflight, open source tenant, open target tenant none
Tenant registry / portfolio launch action Workspace operator / MSP operator Start compare from an existing portfolio review path Registry action Which tenant should I compare next without losing context? current tenant identity and compare launch intent preserved triage filters and return token launch context only none Compare tenants none

Proportionality Review (mandatory when structural complexity is introduced)

  • New source of truth?: no
  • New persisted entity/table/artifact?: no
  • New abstraction?: yes - one narrow compare preview builder and one narrow promotion preflight service
  • New enum/state/reason family?: no new persisted state family; readiness and blocked reasons remain derived from compare/preflight results
  • New cross-domain UI framework/taxonomy?: no
  • Current operator problem: operators can identify tenants that need attention but cannot reach a trustworthy cross-tenant decision without manual reconstruction.
  • Existing structure is insufficient because: existing tenant-level baseline compare pages and portfolio triage state do not support dual-tenant scope or promotion-readiness reasoning.
  • Narrowest correct implementation: derive compare preview and promotion preflight from existing inventory/baseline truth, keep the page canonical and read-only, and audit only the preflight entry points.
  • Ownership cost: maintain one compare page, one preview builder, one preflight service, and a handful of focused tests.
  • Alternative intentionally rejected: actual promotion execution and persisted draft plans were rejected because they would add write risk, queue semantics, and new truth before the compare/preflight workflow is proven.
  • Release truth: current-release workflow gap, not future-release platform speculation

Compatibility posture

This feature assumes a pre-production environment.

Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by this spec.

Canonical replacement is preferred over preservation.

Testing / Lane / Runtime Impact (mandatory for runtime behavior changes)

  • Test purpose / classification: Unit, Feature
  • Validation lane(s): fast-feedback, confidence
  • Why this classification and these lanes are sufficient: unit coverage proves preview matching and promotion-preflight classification without Filament overhead, while focused feature coverage proves page rendering, launch context, audit, and 404/403 semantics on the canonical compare surface.
  • New or expanded test families: one focused PortfolioCompare feature family and one focused Unit/Support/PortfolioCompare family
  • Fixture / helper cost impact: moderate; reuse existing tenant, workspace, inventory, baseline compare, and portfolio-triage fixtures instead of adding browser setup or queue scaffolding
  • Heavy-family visibility / justification: none; do not widen this slice into browser or heavy-governance lanes by default
  • Special surface test profile: standard-native-filament
  • Standard-native relief or required special coverage: ordinary feature coverage is sufficient for the page and launch actions; a small unit test set must prove preflight classification and no-write semantics
  • Reviewer handoff: reviewers must confirm that the slice stays read-only, reuses baseline compare and portfolio seams, preserves deny-as-not-found semantics for inaccessible tenants, and does not smuggle in actual promotion execution
  • Budget / baseline / trend impact: low increase in unit + feature only
  • Escalation needed: none
  • Active feature PR close-out entry: Guardrail
  • Planned validation commands:
    • 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

Scope Boundaries

In Scope

  • one canonical workspace-context compare page for source/target tenant selection
  • read-only compare preview using stable governed-subject identity and existing compare strategy patterns
  • one read-only promotion preflight action that classifies ready, blocked, and manual-mapping subjects
  • workspace audit metadata for preflight entry points
  • launch and return continuity from portfolio-triage/tenant-registry context
  • deep links to existing tenant and baseline compare detail pages instead of duplicated proof surfaces

Non-Goals

  • actual promotion execution or target mutation
  • queueing, retries, or OperationRun
  • persisted compare snapshots or promotion draft tables
  • automatic mapping writers for groups, scope tags, filters, named locations, or app references
  • customer-facing review or compare surfaces
  • cross-workspace compare
  • multi-provider compare frameworks

Assumptions

  • existing inventory and baseline compare seams already provide enough stable subject identity to drive a first compare preview
  • current portfolio-triage return-state patterns are sufficient for launch and back-navigation continuity
  • a read-only preflight is valuable before any write path exists and can be audited without introducing a second persistence truth

Risks

  • some compare subjects may still need provider-specific mapping logic before they can produce a trustworthy readiness result
  • target inventory freshness or missing evidence may block preflight more often than expected and needs explicit reasoning on the page
  • a later implementation could try to add actual promotion execution inside this slice; that must be rejected as scope growth

Follow-up Candidates

  • Cross-tenant promotion execution with preview -> confirmation -> queued run -> verify
  • Managed mapping workflows for named locations, assignments, groups, and filters
  • Cross-tenant decision inbox integration after compare/preflight exists

User Scenarios & Testing (mandatory)

User Story 1 - Compare two authorized tenants (Priority: P1)

As a workspace operator, I want to compare one source tenant to one target tenant from a canonical workspace surface so I can see where governed subjects match, differ, or are missing without reconstructing the answer manually.

Why this priority: This is the smallest valuable slice that turns portfolio visibility into a concrete operator decision surface.

Independent Test: Open the compare page with two authorized tenants, choose governed-subject filters, and verify that the compare preview shows reproducible ready/different/missing results and drill-down links.

Acceptance Scenarios:

  1. Given an operator has access to both selected tenants, When they open the compare page and run the preview, Then they see a structured compare summary grouped by governed-subject state rather than a raw payload diff.
  2. Given the same source and target selection, When the operator reloads or shares the preview URL, Then the compare state is reproducible for the same scoped selection.
  3. Given the operator selects the same tenant as both source and target, When they try to run the preview, Then the page rejects the selection as invalid and does not produce compare or preflight output.

User Story 2 - Generate a promotion preflight without writing (Priority: P1)

As a workspace operator, I want a read-only promotion preflight that tells me what is ready, blocked, or needs manual mapping before any cross-tenant write path exists.

Why this priority: Promotion language is not trustworthy until the product can explain why a target is or is not ready in a bounded, auditable way.

Independent Test: From an authorized compare preview, trigger the preflight action and verify that the page shows readiness counts, blocked reasons, and manual-mapping requirements without mutating source or target tenants.

Acceptance Scenarios:

  1. Given a compare preview contains subjects with stable identity and usable target conditions, When the operator generates a promotion preflight, Then those subjects appear as ready with a clear explanation.
  2. Given some subjects are missing identifiers, stale, or blocked by target conditions, When the operator generates the preflight, Then those subjects appear as blocked or manual-mapping-required with explicit reasons.
  3. Given the operator generates a preflight, When the action completes, Then no target mutation, queued run, or provider write occurs.
  4. Given the operator can view compare but lacks WORKSPACE_BASELINES_MANAGE, When they reach the compare page, Then the preflight action is visibly disabled with permission guidance and any forced request is rejected server-side.

User Story 3 - Launch compare from portfolio context without losing return state (Priority: P2)

As a workspace operator, I want to enter compare from the tenant registry or portfolio-triage context and return without losing my working filters so compare becomes part of the portfolio workflow instead of a detached utility.

Why this priority: The workflow is much less useful if compare starts from scratch and breaks the operator's portfolio-review context.

Independent Test: Launch compare from the tenant registry with active triage filters, verify one tenant is prefilled, and verify the return path restores the prior registry state.

Acceptance Scenarios:

  1. Given the tenant registry has active portfolio-triage filters, When the operator launches compare from a tenant row or contextual action, Then the compare page preserves a return token and prefills the launched tenant as the target tenant.
  2. Given the operator returns from compare, When the registry reloads, Then the prior triage filters are restored.

Edge Cases

  • source and target tenant are the same tenant: reject the selection as invalid input and do not compute preview or preflight
  • source and target tenants belong to different workspaces
  • one selected tenant is no longer visible or never belonged to the actor's scope
  • compare subjects have ambiguous identity or duplicate matches
  • target evidence is stale or missing, making readiness impossible to prove

Requirements (mandatory)

Functional Requirements

  • FR1: The feature MUST provide one canonical workspace-context compare surface for selecting source and target tenants.
  • FR2: The feature MUST enforce workspace membership and source/target tenant entitlement before loading compare data; inaccessible tenants resolve as 404.
  • FR3: The compare preview MUST use stable governed-subject identity and existing inventory/baseline compare seams rather than raw JSON diffing.
  • FR4: The compare preview MUST stay read-only and MUST deep-link to existing tenant or baseline detail surfaces for proof instead of duplicating raw diagnostics locally.
  • FR5: The feature MUST provide a read-only promotion preflight action that classifies subjects as ready, blocked, or manual-mapping-required.
  • FR6: The preflight MUST NOT execute a target write, queue a run, or persist a promotion draft artifact.
  • FR7: The preflight MUST explain blocked and manual states with explicit operator-readable reasons.
  • FR8: The feature MUST reuse existing capability registries with this exact split: page access = WORKSPACE_BASELINES_VIEW, preview data = TENANT_VIEW on both tenants, preflight execution = WORKSPACE_BASELINES_MANAGE.
  • FR9: The feature MUST preserve launch and return continuity from the tenant registry / portfolio-triage path.
  • FR10: The feature MUST record bounded workspace audit metadata for promotion-preflight entry points only.
  • FR11: The compare page MUST reject same-tenant selection before preview or preflight runs.

Non-Functional Requirements

  • NFR1: The feature MUST preserve workspace and tenant isolation and MUST NOT leak source or target hints to unauthorized actors.
  • NFR2: The compare page MUST remain operator-first, decision-first, and must not expose raw payloads by default.
  • NFR3: The implementation MUST remain Filament-native on Livewire v4 and must not introduce a second compare shell or custom status framework.
  • NFR4: The slice MUST not introduce new assets or new globally searchable resources.

Success Criteria

  • SC1: An authorized operator can produce a cross-tenant compare preview from one canonical page without switching across multiple tenant detail surfaces.
  • SC2: The same source, target, and filter selection produces reproducible compare output.
  • SC3: A promotion preflight clearly separates ready, blocked, and manual subjects without performing any write.
  • SC4: Unauthorized source/target combinations remain deny-as-not-found.
  • SC5: View-only members can inspect compare results but cannot execute preflight, and the UI makes that boundary explicit.
  • Program: specs/039-inventory-program/spec.md
  • Core: specs/040-inventory-core/spec.md
  • UI: specs/041-inventory-ui/spec.md
  • Drift: specs/044-drift-mvp/spec.md
  • Foundation follow-up context: docs/product/spec-candidates.md (Cross-Tenant Compare and Promotion v1)