TenantAtlas/specs/319-environment-owned-surface-routing-shell-context-contract/plan.md
ahmido edc33a5a17 spec: add environment-owned surface routing contract (#377)
## Summary\n- add completed Spec 319 artifacts for the environment-owned Baseline Compare routing contract\n- include browser-smoke screenshots and focused validation notes\n- keep the PR diff limited to Spec 319 artifacts because runtime is already present in platform-dev via #374\n\n## Testing\n- git diff --check\n- focused validation recorded in specs/319-environment-owned-surface-routing-shell-context-contract/tasks.md

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #377
2026-05-16 22:55:57 +00:00

25 KiB

Implementation Plan: Environment-Owned Surface Routing & Shell Context Contract

Branch: 319-environment-owned-surface-routing-shell-context-contract | Date: 2026-05-16 | Spec: spec.md Input: Feature specification from /specs/319-environment-owned-surface-routing-shell-context-contract/spec.md

Preparation status: Specification artifacts only. No runtime implementation has been performed by this preparation step.

Summary

Spec 319 hard-cuts Baseline Compare from an ambiguous workspace-style Filament page into an explicit Environment-owned admin surface:

Environment-owned Baseline Compare
-> Workspace + Environment route
-> Workspace + Environment shell
-> copy/breadcrumb/header aligned
-> no clean workspace-only route
-> no ?environment_id filtered hub model
-> no remembered Environment fallback

The implementation must make Baseline Compare self-sufficient through the existing Environment route family, update all Environment Dashboard and Environment-owned entry points, reject cross-workspace Environment access, and keep Decision Register/workspace hub contracts from Specs 314-316 green.

Technical Context

Language/Version: PHP 8.4.15, Laravel 12.52.0 Primary Dependencies: Filament 5.2.1, Livewire 4.1.4, Laravel Sail, Laravel Socialite, Laravel MCP Storage: PostgreSQL; no schema changes planned Testing: Pest 4.3.1 / PHPUnit 12.5.4; focused browser smoke for shell/copy/URL behavior Validation Lanes: fast-feedback, confidence, browser Target Platform: Laravel admin application under apps/platform, local development through Sail, staging/production through Dokploy Project Type: Web application, Laravel/Filament admin panel Performance Goals: No material performance change. Route-bound Environment resolution should reuse existing Workspace/Environment model binding and shell resolution. Constraints: No migrations, seeders, packages, env vars, queues, scheduler, storage, compatibility redirects, legacy query aliases, or broad UI redesign. Scale/Scope: Focused route/shell/context hardening for Baseline Compare, with inspect-only handling for adjacent Environment-owned pages if they show the same mismatch.

UI / Surface Guardrail Plan

  • Guardrail scope: Baseline Compare route/shell/copy/navigation contract and Environment Dashboard CTA URL generation.
  • Native vs custom classification summary: Existing Filament Page/Blade view and existing Environment Dashboard/Widget links. No new styling system.
  • Shared-family relevance: Navigation, dashboard CTA/action links, shell context, breadcrumbs, and OperationRun action links.
  • State layers in scope: Route params, shell context, Filament Page access, page copy, browser reload/history, CTA URL query.
  • Audience modes in scope: Operator-MSP and support-platform.
  • Decision/diagnostic/raw hierarchy plan: Baseline Compare remains a decision-support Environment posture page. Diagnostics/evidence details remain on existing page sections.
  • Raw/support gating plan: No raw/support evidence exposure change.
  • One-primary-action / duplicate-truth control: Environment route + shell become the single ownership truth; no hidden query/session fallback.
  • Handling modes by drift class or surface: Hard-stop for workspace-style Baseline Compare render; review-mandatory for any adjacent Environment-owned page touched.
  • Repository-signal treatment: Spec 318 browser evidence is the starting signal; implementation must produce tests and focused browser evidence.
  • Special surface test profiles: global-context-shell.
  • Required tests or manual smoke: route/classification tests, CTA URL tests, remembered-fallback rejection, cross-workspace rejection, Decision Register regression, browser smoke.
  • Exception path and spread control: If old URL redirect is unavoidable, it must be explicitly justified, validate Workspace/Environment relationship, emit no legacy alias support, and be covered by tests. Preferred path is no route/404.
  • Active feature PR close-out entry: Guardrail and Smoke Coverage.

Shared Pattern & System Fit

  • Cross-cutting feature marker: yes.
  • Systems touched: BaselineCompareLanding, AdminPanelProvider page registration, route definitions if explicit page route is needed, AdminSurfaceScope, WorkspaceScopedEnvironmentRoutes, WorkspaceSidebarNavigation, ManagedEnvironmentLinks, OperateHubShell, Environment Dashboard summary/actions/widgets, Baseline Compare tests, workspace hub regression tests.
  • Shared abstractions reused: Existing Environment route pattern under /admin/workspaces/{workspace}/environments/{environment}/..., WorkspaceScopedEnvironmentRoutes where practical, ManagedEnvironmentLinks, existing OperateHubShell, existing OperationRun UX helpers.
  • New abstraction introduced? why?: none planned.
  • Why the existing abstraction was sufficient or insufficient: Existing Environment-bound pages work correctly; Baseline Compare bypasses them by using /admin/baseline-compare-landing plus environment_id/remembered context.
  • Bounded deviation / spread control: Any helper added must replace the current query-based Baseline Compare URL path, not add a second supported path.

OperationRun UX Impact

  • Touches OperationRun start/completion/link UX?: yes, existing Baseline Compare action is high-impact but no new OperationRun lifecycle is introduced.
  • Central contract reused: OperationUxPresenter, OpsUxBrowserEvents, OperationRunLinks, existing Baseline Compare service behavior.
  • Delegated UX behaviors: queued toast, Open operation URL, run-enqueued browser event, tenant/workspace-safe URL resolution.
  • Surface-owned behavior kept local: Compare initiation button and Baseline Compare page state remain local.
  • Queued DB-notification policy: N/A.
  • Terminal notification path: Existing central lifecycle behavior.
  • Exception path: none.

Provider Boundary & Portability Fit

  • Shared provider/platform boundary touched?: yes.
  • Provider-owned seams: Microsoft/Intune compare details and provider tenant identity remain unchanged.
  • Platform-core seams: Workspace/Environment route ownership, shell classification, and query/fallback rejection.
  • Neutral platform terms / contracts preserved: Workspace, Environment, Environment-owned page, Workspace hub, Environment filter.
  • Retained provider-specific semantics and why: Existing baseline compare content still compares Microsoft/Intune-governed subjects because that is the current provider implementation.
  • Bounded extraction or follow-up path: Workspace-owned baseline analysis pages are deferred to Spec 320.

Constitution Check

GATE: Must pass before implementation. Re-check after runtime changes.

  • Inventory-first: no inventory/snapshot truth change.
  • Read/write separation: no new write behavior. Existing Compare Now remains confirmation + authorization + audit/OperationRun governed.
  • Graph contract path: no new Graph calls; page render must remain DB-only.
  • Deterministic capabilities: existing capability checks remain; tests must cover route/access boundaries.
  • RBAC-UX: non-member / not entitled Workspace or Environment access returns 404/safe no-access; member missing capability follows existing behavior.
  • Workspace isolation: route model must validate Environment belongs to Workspace.
  • Tenant isolation: no cross-workspace Environment leakage; no provider tenant ID lookup.
  • Run observability: existing Baseline Compare run behavior remains observable through OperationRun.
  • Test governance: lane, fixture cost, browser coverage, and reviewer handoff are explicit in spec/plan/tasks.
  • Proportionality: no new persisted truth or broad abstraction; route replacement is the narrowest correct fix.
  • No premature abstraction: reuse existing route/shell helpers.
  • Persisted truth: no tables/entities/artifacts added.
  • Behavioral state: no new state/status/reason family.
  • UI semantics: route/shell/copy direct mapping only.
  • Shared pattern first: reuse Environment route family and shared OperationRun helpers.
  • Provider boundary: route identity uses platform Environment, not provider tenant ID.
  • V1 explicitness / few layers: hard cutover, no compatibility layer.
  • Filament-native UI: no ad-hoc styling or published internals.
  • Filament v5 / Livewire v4: Livewire 4.1.4 satisfies Filament v5 requirement; no Livewire v3 APIs.
  • Provider registration: Laravel 12 panel providers remain in apps/platform/bootstrap/providers.php; this spec should not register panel providers in bootstrap/app.php.
  • Global search: no globally searchable resource behavior should change. If a touched Resource is affected, verify View/Edit/global-search status remains valid or disabled.
  • Destructive actions: no destructive action is added. Existing Compare Now high-impact action must keep ->action(...), ->requiresConfirmation(), capability authorization, OperationRun UX, and audit behavior.
  • Asset strategy: no Filament assets planned; no new filament:assets deployment requirement.

Test Governance Check

  • Test purpose / classification by changed surface: Unit for classifier/registry paths; Feature/Livewire for route/access/render/action URL contracts; Browser for integrated URL/shell/copy/reload/history behavior.
  • Affected validation lanes: fast-feedback, confidence, browser.
  • Why this lane mix is the narrowest sufficient proof: The defect spans code classification and browser-visible shell/copy; both must be proven.
  • Narrowest proving command(s):
    • cd apps/platform && ./vendor/bin/sail artisan test --filter=BaselineCompare
    • cd apps/platform && ./vendor/bin/sail artisan test --filter=AdminSurfaceScope
    • cd apps/platform && ./vendor/bin/sail artisan test --filter=WorkspaceHubRegistry
    • cd apps/platform && ./vendor/bin/sail artisan test --filter=DecisionRegister
    • focused browser smoke for Spec 319
    • cd apps/platform && ./vendor/bin/sail pint --test
    • git diff --check
  • Fixture / helper / factory / seed / context cost risks: Existing workspace/environment/member setup plus baseline assignment/snapshot/run fixtures. Keep setup local to the new tests and avoid broad shared defaults.
  • Expensive defaults or shared helper growth introduced?: no.
  • Heavy-family additions, promotions, or visibility changes: Focused browser smoke only. Durable browser no-drift family is Spec 322.
  • Surface-class relief / special coverage rule: global-context-shell requires browser verification.
  • Closing validation and reviewer handoff: Reviewers must verify old URL invalidation, no query alias support, no remembered fallback, cross-workspace 404, and Decision Register regression.
  • Budget / baseline / trend follow-up: none expected.
  • Review-stop questions: Does any Baseline Compare path still render from /admin/baseline-compare-landing? Does any CTA still emit environment_id? Does any code path use remembered Environment to render Baseline Compare? Are Baselines/Baseline Snapshots accidentally pulled into this spec?
  • Escalation path: follow-up-spec for 320/321/322 only; update this spec if implementation reveals a route architecture blocker.
  • Active feature PR close-out entry: Guardrail and Smoke Coverage.
  • Why no dedicated follow-up spec is needed: Spec 319 is the dedicated Baseline Compare Environment-owned route fix.

Project Structure

Documentation (this feature)

specs/319-environment-owned-surface-routing-shell-context-contract/
|-- spec.md
|-- plan.md
|-- tasks.md
|-- checklists/
|   `-- requirements.md
`-- artifacts/
    `-- screenshots/

No research.md, data-model.md, quickstart.md, or contracts/ artifact is required for preparation because this feature introduces no new data model, external API contract, or standalone workflow API.

Source Code (repository root)

Likely runtime files to inspect or update during implementation:

apps/platform/routes/web.php
apps/platform/app/Providers/Filament/AdminPanelProvider.php
apps/platform/app/Filament/Pages/BaselineCompareLanding.php
apps/platform/app/Filament/Concerns/UsesAdminEnvironmentFilterQueryParameter.php
apps/platform/app/Filament/Concerns/WorkspaceScopedEnvironmentRoutes.php
apps/platform/app/Support/Navigation/AdminSurfaceScope.php
apps/platform/app/Support/Navigation/WorkspaceHubRegistry.php
apps/platform/app/Support/Navigation/WorkspaceSidebarNavigation.php
apps/platform/app/Support/ManagedEnvironmentLinks.php
apps/platform/app/Support/OperateHub/OperateHubShell.php
apps/platform/app/Support/Middleware/EnsureEnvironmentContextSelected.php
apps/platform/app/Filament/Pages/EnvironmentDashboard.php
apps/platform/app/Support/EnvironmentDashboard/EnvironmentDashboardSummaryBuilder.php
apps/platform/app/Filament/Widgets/ManagedEnvironment/BaselineCompareCoverageBanner.php
apps/platform/app/Filament/Widgets/Dashboard/BaselineCompareNow.php
apps/platform/app/Filament/Widgets/Dashboard/NeedsAttention.php
apps/platform/app/Filament/Pages/BaselineCompareMatrix.php
apps/platform/resources/views/filament/pages/baseline-compare-landing.blade.php
apps/platform/tests/Unit/Tenants/AdminSurfaceScopeTest.php
apps/platform/tests/Feature/Navigation/WorkspaceHubRegistryTest.php
apps/platform/tests/Feature/Workspaces/GlobalContextShellContractTest.php
apps/platform/tests/Feature/Filament/PanelNavigationSegregationTest.php
apps/platform/tests/Feature/Filament/BaselineCompareLandingAdminTenantParityTest.php
apps/platform/tests/Feature/Filament/BaselineCompareLandingStartSurfaceTest.php
apps/platform/tests/Feature/Governance/DecisionRegisterWorkspaceHubContractTest.php
apps/platform/tests/Feature/Navigation/WorkspaceHubEnvironmentFilterContractTest.php
apps/platform/tests/Feature/Navigation/WorkspaceHubClearFilterContractTest.php
apps/platform/tests/Browser/

Related pages to inspect, not automatically rewrite:

apps/platform/app/Filament/Pages/EnvironmentRequiredPermissions.php
apps/platform/app/Filament/Pages/EnvironmentDiagnostics.php
apps/platform/app/Filament/Pages/InventoryCoverage.php
apps/platform/app/Filament/Resources/InventoryItemResource.php
apps/platform/app/Filament/Resources/BackupScheduleResource.php
apps/platform/app/Filament/Resources/BackupSetResource.php
apps/platform/app/Filament/Resources/RestoreRunResource.php
apps/platform/app/Filament/Resources/FindingResource.php
apps/platform/app/Filament/Resources/FindingExceptionResource.php

Explicitly out-of-scope unless analysis proves Environment ownership:

apps/platform/app/Filament/Resources/BaselineProfileResource.php
apps/platform/app/Filament/Resources/BaselineSnapshotResource.php
apps/platform/app/Filament/Pages/BaselineCompareMatrix.php
apps/platform/app/Filament/Pages/CrossEnvironmentComparePage.php
apps/platform/app/Filament/Pages/Monitoring/AuditLog.php
apps/platform/app/Filament/Pages/Monitoring/Alerts.php
apps/platform/app/Filament/Resources/Alert*

Structure Decision: Laravel/Filament platform app under apps/platform; any runtime changes stay inside existing Page, Support, route, view, and test locations.

Complexity Tracking

Violation Why Needed Simpler Alternative Rejected Because
None planned Route ownership can use existing Environment route/shell mechanisms New compatibility adapters, query resolvers, or route frameworks are explicitly rejected

Proportionality Review

  • Current operator problem: Baseline Compare renders Environment-specific copy/data through hidden context instead of explicit route ownership.
  • Existing structure is insufficient because: /admin/baseline-compare-landing cannot self-sufficiently establish Environment context after clear/reload and should not use environment_id as a workspace hub filter.
  • Narrowest correct implementation: Make Baseline Compare route-bound to Workspace + Environment, update links, and reject old access.
  • Ownership cost created: Focused tests and browser evidence for a high-risk page.
  • Alternative intentionally rejected: Supporting both old and new routes, redirecting old URLs by default, or accepting environment_id as canonical page context.
  • Release truth: Current-release hardening before production.

Phase 0: Discovery Completed During Preparation

Repository facts confirmed before authoring this plan:

  • Current branch before Spec Kit creation was platform-dev, clean, at 1c27af4f spec: add admin surface scope shell context audit (#373).
  • No existing 319-* branch or specs/319-* package was present.
  • Spec Kit script created branch 319-environment-owned-surface-routing-shell-context-contract and specs/319-environment-owned-surface-routing-shell-context-contract/spec.md.
  • Spec 318 is analysis-only and does not have plan/tasks files; it provides mismatch-findings.md, audit-report.md, recommended-fixes.md, page-matrix.md, screenshots, and state captures.
  • Spec 318 finding M1: Baseline Compare URLs are not self-sufficient. Direct clean and direct environment_id URLs return 403 after clearing Environment context; environment sidebar works only with remembered context.
  • Spec 318 recommended fix: route-bound Environment URL under /admin/workspaces/{workspace}/environments/{environment}/baseline-compare.
  • BaselineCompareLanding currently extends Page, is registered in AdminPanelProvider, uses ResolvesPanelTenantContext and UsesAdminEnvironmentFilterQueryParameter, and loads stats through BaselineCompareStats::forTenant().
  • BaselineCompareLanding::canAccess() returns false without ManagedEnvironment from resolveTenantContextForCurrentPanel().
  • UsesAdminEnvironmentFilterQueryParameter appends environment_id to the unbound page URL when tenant is provided.
  • EnvironmentDashboardSummaryBuilder, BaselineCompareCoverageBanner, BaselineCompareNow, NeedsAttention, and BaselineCompareMatrix currently generate Baseline Compare links via BaselineCompareLanding::getUrl(tenant: $tenant).
  • WorkspaceHubRegistry does not list Baseline Compare; this is correct and must remain true.
  • AdminSurfaceScope currently treats /admin/workspaces/{workspace}/environments/{environment}/... as EnvironmentBound.
  • EnsureEnvironmentContextSelected already configures environment navigation for environment surfaces and workspace sidebar for workspace surfaces.
  • Existing test BaselineCompareLandingAdminTenantParityTest asserts access from remembered admin tenant; this likely asserts now-invalid behavior and must be replaced/updated.

Technical Approach

1. Establish canonical Environment route

Preferred route shape:

/admin/workspaces/{workspace}/environments/{environment}/baseline-compare

Use the repo's existing route-bound Environment pattern. The route should:

  • run through admin panel middleware and workspace membership checks;
  • use existing workspace route binding;
  • use {environment:slug} or the established ManagedEnvironment route key;
  • validate Environment belongs to Workspace;
  • fail safe with 404/safe no-access on mismatch or missing entitlement;
  • produce AdminSurfaceScope::EnvironmentBound.

Implementation should prefer one canonical route and remove/disable the old unbound page route. If Filament Page auto-registration cannot be moved directly, wrap or override route generation narrowly without creating a compatibility layer.

2. Update Baseline Compare URL generation

Replace query-based Baseline Compare URL generation with route-owned URL generation for:

  • Environment Dashboard summary/action payloads;
  • managed-environment Baseline Compare coverage banner;
  • dashboard Baseline Compare widget;
  • Needs Attention widget;
  • Baseline Compare Matrix per-Environment links;
  • any direct BaselineCompareLanding::getUrl(tenant: $tenant) call that opens the Environment-owned page.

The generated URL must not include:

environment_id
tenant
tenant_id
managed_environment_id
tenant_scope
tableFilters

3. Remove remembered fallback for Baseline Compare

Baseline Compare must render only when route context resolves an Environment. Implementation must not:

  • rely on WorkspaceContext::rememberedEnvironment();
  • rely on Filament::getTenant() as fallback for old URL;
  • infer from provider tenant identifiers;
  • treat environment_id as page context;
  • silently switch Workspace.

If BaselineCompareLanding keeps ResolvesPanelTenantContext, its route/category must force the context to come from the route.

4. Classification and navigation

Baseline Compare should classify as Environment-owned through the Environment route. Tests must prove:

  • new canonical path is AdminSurfaceScope::EnvironmentBound;
  • old path is not a workspace hub and does not render;
  • WorkspaceHubRegistry does not include Baseline Compare;
  • workspace sidebar does not show Baseline Compare as a workspace-wide hub;
  • environment sidebar/action placement remains Environment-owned if present.

5. Cross-workspace and no-access protection

Route/model resolution must enforce:

(int) $environment->workspace_id === (int) $workspace->getKey()

Invalid combinations return 404/safe no-access. Do not leak Environment existence, switch workspace, or fallback to remembered Environment.

6. Shell, breadcrumb, copy

The valid page must show:

  • selected Workspace;
  • selected Environment;
  • Baseline Compare header/title;
  • breadcrumb or equivalent ownership signal;
  • "this environment" copy only with active Environment shell.

When Environment context is missing, the page should not render.

7. Regression scope

Keep these contracts unchanged:

  • Decision Register clean workspace hub route;
  • Decision Register environment_id filtered workspace hub route with visible chip;
  • clear filter behavior from Spec 316;
  • legacy tenant cleanup from Spec 317;
  • workspace hubs from Specs 314-316.

Implementation Phases

  1. Guardrail and discovery: re-check branch/status, confirm exact current route names, inspect affected Baseline Compare links, and record the old route behavior.
  2. Tests first/alongside: add classification, access, route, CTA, remembered fallback, cross-workspace, copy/shell, and Decision Register regression tests.
  3. Route/link implementation: make canonical Environment route authoritative and update Baseline Compare URL generators.
  4. Shell/copy/navigation cleanup: remove query/remembered fallback assumptions and ensure breadcrumbs/shell align.
  5. Related page inspection: only fix pages with the same proven Environment-owned mismatch.
  6. Validation: focused Pest, browser smoke, formatting, git diff --check, and implementation close-out.

Migration / Data / Deployment Impact

  • Migrations: none planned.
  • Seeders: none planned.
  • Packages: none planned.
  • Env vars: none planned.
  • Queues/scheduler/storage: no changes planned.
  • Filament assets: no new assets planned; no new filament:assets deployment step.
  • Dokploy/Sail: ordinary code deployment only if implementation changes runtime files.

Browser Verification Plan

Use the in-app browser or the repository's browser smoke workflow against local Sail once runtime changes exist.

Required screenshots:

specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/environment-cta--baseline-compare.png
specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/direct-clean--baseline-compare--rejected.png
specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/direct-filtered--baseline-compare--rejected.png
specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/baseline-compare--after-reload.png
specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/baseline-compare--back-forward.png
specs/319-environment-owned-surface-routing-shell-context-contract/artifacts/screenshots/decision-register--regression.png

If a route is fully absent and cannot be screenshotted, document route absence in the close-out and capture the nearest browser proof if useful.

Risks and Controls

  • Risk: Old tests assert remembered Environment fallback. Control: Replace them with no-remembered-fallback and canonical route tests.
  • Risk: Filament auto page registration keeps old slug alive. Control: Use the narrowest supported page route override or explicit Laravel route pattern and test old URL invalidation.
  • Risk: Link helpers spread query-based Baseline Compare URLs. Control: rg all BaselineCompareLanding::getUrl, baseline-compare-landing, and environment_id occurrences and update only in-scope Baseline Compare links.
  • Risk: Workspace-owned baseline pages get pulled into scope. Control: Keep Baselines/Baseline Snapshots/Matrix out unless proven Environment-owned; record follow-up 320.
  • Risk: Browser flow depends on local fixture data. Control: Use existing local fixture environment from Spec 318 where possible or create test data through normal app factories/tests; document browser blockers.

Readiness Gate

Spec 319 is implementation-ready when:

  • spec.md, plan.md, tasks.md, and checklists/requirements.md exist.
  • The scope is Baseline Compare-first and bounded.
  • No open question blocks implementation.
  • Required tests and browser flows are named.
  • No application implementation has happened during prep.
  • Candidate Selection Gate and Spec Readiness Gate pass.