TenantAtlas/specs/338-workspace-environment-resource-scope-contract/spec.md
ahmido e0c2cdb1f4 feat: enforce workspace and environment scope contract (Spec 338) (#409)
## Summary
- enforce the canonical workspace/environment scope contract for workspace hubs and environment-owned surfaces
- replace first-party Operations deep links that leaked Filament `tableFilters[...]` internals with stable product-level query behavior
- add the sidebar scope indicator and split environment-page navigation into explicit `Workspace-wide` and `Workspace admin` groups
- remove redundant tenantless `All environments` scope badges from workspace-wide pages while preserving explicit environment filter affordances
- include the Spec 338 artifacts, guard tests, and browser smoke coverage for the new contract

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Navigation/Spec338EnvironmentSidebarSeparationTest.php tests/Feature/Navigation/Spec338OperationRunLinksQueryContractTest.php tests/Feature/Navigation/Spec338SidebarScopeIndicatorTest.php tests/Feature/Filament/PanelNavigationSegregationTest.php`
- `cd apps/platform && ./vendor/bin/sail php vendor/bin/pest tests/Browser/Spec338ScopeContractSmokeTest.php --compact`

## Notes
- Livewire v4 compliance unchanged
- Filament provider registration remains in `bootstrap/providers.php`
- no destructive action behavior changed
- no migrations, env var changes, or new Filament asset registration

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #409
2026-05-31 01:36:08 +00:00

260 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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**: TenantPilots 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 pages 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 specs 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=<canonical-code>`
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)