# Feature Specification: Spec 338 - Workspace / Environment Resource Scope Contract **Feature Branch**: `338-workspace-environment-resource-scope-contract` **Created**: 2026-05-30 **Status**: Draft **Input**: User-provided Spec 338 draft (“Contract-/Guard-Spec” for workspace/environment resource ownership + link/query hygiene) ## Spec Candidate Check *(mandatory — SPEC-GATE-001)* - **Problem**: TenantPilot’s workspace/environment scope foundations exist (Specs 311/319/320/321), but remaining link/query seams and navigation registration can still encode “mixed ownership” (workspace-owned surfaces appearing environment-owned, or workspace hub filters encoded as hidden context / framework internals). - **Today's failure**: - First-party deep links can still emit Filament internal query keys (notably `tableFilters[...]`) instead of a stable product-level contract. - Environment → workspace hub links can drift between “route scope” and “filter scope” depending on the helper used. - Evidence has legacy `/admin/evidence/*` classification/special casing that must be either proven real and intentional, or removed as stale to reduce ambiguity. - **User-visible improvement**: Operators can trust that: - route scope determines shell/sidebar; - workspace hubs filter by a stable, explicit query contract (`environment_id`, and where needed `operation_type`); - environment navigation does not claim workspace-owned portfolio surfaces as environment-owned; - the sidebar exposes a direct scope signal so workspace-level and environment-level pages are distinguishable without reading the URL. - workspace-wide pages do not render a generic “All environments” header/scope badge when the page is already tenantless; explicit environment filters remain visible through filter banners and table chips. - **Smallest enterprise-capable version**: Document the canonical ownership taxonomy and enforce only the highest-risk seams with tests: - stop first-party helpers from emitting `tableFilters[...]` for hub deep links (especially Operations), - ensure Evidence scope is explicit (workspace hub vs environment-owned resources), - keep baseline ownership/navigation contract regression-proof (no reopen of Spec 320; fix only if regression is proven). - **Explicit non-goals**: - no broad UI redesign of the admin shell, sidebars, or page layouts, - no route restructuring (keep canonical route families as-is), - no workspace/environment data model or schema changes, - no Provider Connections “scope split” feature (defer to the already-listed candidate), - no rewrite of Spec 311/320 behavior unless a regression is proven by tests. - **Permanent complexity imported**: narrow link/query contract mapping (`operation_type` deep-link), a small set of guard tests, and clarified operator-copy expectations (only where proven misleading). - **Why now**: Current productization and audit lanes depend on stable, explicit scope and deep links; leaving internal query keys in first-party helpers makes future specs copy the wrong contract. - **Why not local**: Fixing one page’s deep link without a contract + guard tests leaves the next helper or navigation entry free to reintroduce the same ambiguity. - **Approval class**: Cleanup / Consolidation (contract + guard hardening over existing foundations). - **Red flags triggered**: Cross-surface contract + shared link helper changes. **Defense**: scope is bounded to confirmed seams; no new taxonomy framework or persisted truth; tests enforce stability. - **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 1 | Komplexität: 1 | Produktnähe: 2 | Wiederverwendung: 2 | **Gesamt: 10/12** - **Decision**: approve. ## Summary TenantPilot already has strong workspace/environment scope foundations. This spec locks down a **resource ownership + link/query contract** so that: 1) workspace-owned surfaces stay workspace-owned (even when entered from environment context), 2) workspace hubs are filtered only via explicit, product-level query keys (`environment_id`, and optionally `operation_type`), 3) environment-owned detail surfaces remain environment-route-owned, 4) first-party helpers stop emitting Filament table internals (`tableFilters[...]`) as canonical deep link contract, 5) the sidebar presents explicit workspace vs environment scope identity. This is a contract-first spec with targeted runtime fixes only. ## Spec Scope Fields *(mandatory)* - **Scope**: canonical-view (navigation + link/query contract) - **Primary Routes (representative)**: - Workspace hubs: `/admin/workspaces/{workspace}/operations`, `/admin/evidence/overview`, `/admin/alerts`, `/admin/audit-log` - Workspace-owned portfolio surfaces: `/admin/baseline-profiles`, `/admin/baseline-snapshots` - Environment-owned detail surfaces: `/admin/workspaces/{workspace}/environments/{environment}/...` - **Data Ownership**: no ownership model change. This spec is about UI scope signals + link/query contracts, not table ownership. - **RBAC**: no new capabilities. Existing workspace membership and tenant/environment membership continue to gate visibility and access. For canonical-view specs, the spec MUST define: - **Default filter behavior when tenant-context is active**: - Workspace hubs MUST NOT silently infer environment filtering from remembered environment/topbar selection. - If a workspace hub is filtered, it MUST be via explicit query (`environment_id`) or explicit visible UI filter state. - Environment-owned routes MUST include the environment in the route (no query-derived environment ownership). - **Explicit entitlement checks preventing cross-tenant leakage**: - `environment_id` MUST be validated as “belongs to current workspace” AND “actor is entitled”; otherwise ignore/deny safely. - Canonical deep links must not widen scope through implicit session context. ## Canonical Scope Taxonomy (product contract) Every reachable surface is classified as exactly one: ### A. Workspace-owned source of truth Workspace-owned; may aggregate across environments; does not require an environment route. ### B. Workspace hub with optional local environment filter Workspace-owned monitoring/governance hubs that may filter by environment via explicit query/UI. Rules: - Route determines shell (workspace shell). - Public filter query key is `environment_id`. - Hubs must not infer filters from topbar “remembered environment”. ### C. Environment-owned detail surface Belongs to exactly one Managed Environment. Rules: - Route includes workspace + environment: `/admin/workspaces/{workspace}/environments/{environment}/...` - Environment is not optional or query-derived. ### D. Cross-environment / portfolio aggregation Compares/aggregates across multiple environments; must not pretend to be “current environment owned”. ### E. Platform / system / utility System pages (`/system`, auth callbacks, choosers). Must not create hidden environment filters. ### F. Invalid / needs split Any surface that mixes route scope, navigation scope, and data scope such that the operator cannot tell “what owns this”. ## Routing / Link Contract ### WorkspaceLink Workspace-owned surface without environment filter. ### EnvironmentLink Environment-owned surface with environment in the route. ### WorkspaceFilteredLink Workspace-owned hub filtered to one environment via explicit query. Allowed public filter keys: - `environment_id` (canonical) - `operation_type` (Operations-only, optional; see required decision D1) Forbidden as **first-party helper output** for hub scope (canonical deep-link contract): - `tenant` - `tenant_id` - `managed_environment_id` - `tenant_scope` - `tableFilters` Note: Filament may still persist table state in the URL after user interactions. This spec’s restriction is about **first-party helper outputs** and **canonical deep links**, not about banning every possible `tableFilters` appearance after manual operator filtering. ## Required Runtime Decisions ### D1 — OperationRunLinks operation type filter (confirmed repo seam) Repo evidence: `apps/platform/app/Support/OperationRunLinks.php` currently emits `tableFilters[type][value]` when `operationType` is provided. Decision: - `tableFilters[...]` must not be emitted by first-party helpers for operation-type deep links. - If operation-type deep-linking is needed, use a stable query key: - `operation_type=` Acceptance: - `OperationRunLinks::index(..., operationType: ...)` does not emit `tableFilters`. - Operations page accepts `operation_type` and translates it into local table state, **or** operation-type deep links are removed (prefer correctness over leaking internals). ### D2 — Evidence route special casing (confirmed repo seam) Repo evidence: - `/admin/evidence/overview` is a workspace hub route (`admin.evidence.overview`). - `apps/platform/app/Http/Controllers/ClearEnvironmentContextController.php` and `apps/platform/app/Support/Navigation/AdminSurfaceScope.php` contain legacy handling/classification for `/admin/evidence/*` paths. Decision: - Keep Evidence Overview as workspace hub, with optional explicit `environment_id` filter. - Confirm whether any `/admin/evidence/*` non-overview paths are still real and intended: - If not real, remove/neutralize stale classification branches. - If real, document the intended contract and stop treating it as “mystery scope”. Acceptance: - No ambiguous third “environment-scoped evidence under `/admin/evidence/*`” remains without explicit contract + test. ### D3 — Baseline ownership & navigation (regression-only) Repo evidence: Spec 320 completed classification for baseline library surfaces as workspace-owned analysis. Decision: - Do not reopen baseline ownership decisions in Spec 338. - Only change baseline navigation registration if a regression is proven by tests or UI contract failures on current branch. Acceptance: - Baseline Profiles/Snapshots remain workspace-owned surfaces; environment navigation must not claim them as environment-owned. ## UI Surface Impact *(mandatory — UI-COV-001)* - [ ] No UI surface impact - [x] Existing page changed - [ ] New page/route added - [x] Navigation changed - [x] Filament panel/provider surface changed - [ ] New modal/drawer/wizard/action added - [ ] New table/form/state added - [ ] Customer-facing surface changed - [ ] Dangerous action changed - [ ] Status/evidence/review presentation changed - [x] Workspace/environment context presentation changed ## Scope Badge Contract Addendum - Tenantless workspace-wide pages MUST NOT render a generic “All environments” header action or workbench badge as the primary context signal. - When `environment_id` is present on a workspace hub, the explicit filter banner/chip is the source of truth for the narrowed dataset. - Environment-scoped shell labels remain valid only when the route truly resolves to an environment-owned context. ## UI/Productization Coverage *(UI-COV-001)* - **Route/page/surface**: Operations hub deep links; Evidence Overview hub; environment sidebar vs workspace sidebar entries and scope identity, including separated workspace-wide/admin groups on environment-owned pages (baseline library surfaces, regression-only) - **Current page archetype**: Monitoring hub (Operations/Evidence); navigation shell contract - **Design depth**: Domain Pattern Surface (contract hardening, minimal visual work) - **Repo-truth level**: repo-verified (Spec 311/320/322 + current helper code) - **Existing pattern reused**: `AdminSurfaceScope`, `WorkspaceHubRegistry`, Filament `SIDEBAR_NAV_START` render hook, Filament navigation groups, `CanonicalNavigationContext`, `OperationRunLinks` - **New pattern required**: small scope-aware workspace hub navigation helper, limited to grouping and environment filter URL carry for existing hub entries - **Screenshot required**: yes, only for scope-regression proof in the implementation PR (light/dark where relevant) - **Page audit required**: no (existing archetypes; update coverage artifacts only if new navigation entries are introduced) - **Dangerous-action review required**: no (no destructive action changes) - **Coverage files to update (in implementation PR)**: - [ ] `docs/ui-ux-enterprise-audit/route-inventory.md` (only if navigation entries/routes change) - [ ] `docs/ui-ux-enterprise-audit/design-coverage-matrix.md` (only if new surface created; expected `no`) - [x] `N/A - no new reachable UI surface added; contract hardening only` ## Cross-Cutting / Shared Pattern Reuse *(mandatory)* - **Cross-cutting feature?**: yes - **Interaction class(es)**: navigation entry points, scope presentation, deep links, hub filtering - **Systems touched**: `AdminSurfaceScope`, `WorkspaceHubRegistry`, `WorkspaceHubNavigation`, Filament sidebar render hook/navigation groups, `CanonicalNavigationContext`, `OperationRunLinks`, `ClearEnvironmentContextController` - **Existing pattern(s) to extend**: canonical workspace/environment scope contract (Specs 311/320/322) - **Allowed deviation and why**: none (prefer tightening existing helpers over new frameworking) - **Consistency impact**: “Route determines shell; query determines filter; helpers emit canonical keys.” - **Review focus**: no new scope magic; no helper outputs that encode Filament internals as canonical contract. ## OperationRun UX Impact - **Touches OperationRun start/completion/link UX?**: yes (link semantics only) - **Shared OperationRun UX contract/layer reused**: `App\\Support\\OperationRunLinks` - **Delegated behaviors**: operation collection URL generation; environment filter key; operation type deep link key - **Queued DB-notification policy**: `N/A` - **Terminal notification path**: `N/A` - **Exception required?**: none ## Provider Boundary / Platform Core Check - **Shared provider/platform boundary touched?**: yes (terminology + query keys must not reintroduce legacy “tenant” meaning at platform scope) - **Boundary classification**: platform-core (workspace/environment scope) + provider-owned (Entra “tenant” identity) must remain separated - **Seams affected**: query keys and helper naming only - **Why this does not deepen provider coupling accidentally**: enforce `environment_id`/`operation_type` at platform scope; keep `tenant` terminology provider-boundary-only. ## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)* - **Test purpose / classification**: Feature (contract tests) + Browser (minimal smoke for sidebar/scope) - **Validation lane(s)**: fast-feedback (Feature) + browser (smoke), no heavy-governance required for this slice ## Acceptance Criteria - **AC1**: First-party deep links use canonical query keys (`environment_id`, and where needed `operation_type`), not `tableFilters[...]`. - **AC2**: Evidence scope is explicit: Evidence Overview is a workspace hub; any remaining `/admin/evidence/*` special casing is either removed as stale or documented + tested as intentional. - **AC3**: Baseline library ownership remains workspace-owned and does not regress (no baseline ownership reopen). - **AC4**: Targeted tests are green (feature contract tests + minimal browser smoke if UI navigation is involved). - **AC5**: Workspace-owned and environment-owned pages show an explicit sidebar scope indicator that names the active workspace or environment, while tenantless workspace topbars and environment pickers do not render a negative “No environment selected” status. - **AC6**: Environment-owned sidebars separate workspace-wide/admin links into clearly labeled groups and carry `environment_id` only to workspace hubs that support explicit environment filtering. - **AC7**: Managed Environments registry pages do not duplicate the `/admin/choose-environment` flow with a redundant “Choose environment” CTA; environment cards remain the entry point, with Add Environment and Switch Workspace as the supporting actions. ## Follow-up spec candidates - Provider Connection Scope Hardening (credential-adjacent authority semantics) - Canonical Link / Query Cleanup (broader inventory + replacement beyond Operations/Evidence) - Environment Resource Context Follow-through (reduce hidden context reliance inside environment-owned resources)