# Feature Specification: Spec 339 - Provider Connection Scope Hardening **Feature Branch**: `339-provider-connection-scope-hardening` **Created**: 2026-05-31 **Status**: Draft **Input**: Candidate `provider-connection-scope-hardening` from `docs/product/spec-candidates.md` + user-provided draft + repo inspection of Provider Connection scope/authority seams. ## Spec Candidate Check *(mandatory — SPEC-GATE-001)* - **Problem**: Provider Connections are credential-adjacent. Their scope and authorization authority must not come from hidden UI/session state (remembered environment) or Filament tenant fallbacks. - **Today's failure**: - Provider Connection create authorization can still fall back to remembered environment (`WorkspaceContext::lastEnvironmentId`) or Filament tenant (`Filament::getTenant()`), allowing “implicit target environment” when `environment_id` is missing. - Workspace authority can be inferred via Filament tenant when workspace context is missing, which is unsafe for a credential-adjacent surface. - Legacy query keys (`tenant`, `tenant_id`, `managed_environment_id`) still exist in the ecosystem and must not regain authority via accidental parsing. - **User-visible improvement**: Operators can trust that Provider Connection list/view/create/edit/actions are scoped only by: - explicit workspace context (required), plus - explicit `environment_id` (filter/create), or - record ownership (`ProviderConnection.workspace_id` + `ProviderConnection.managed_environment_id`) for view/edit/actions. - **Smallest enterprise-capable version**: - define and enforce an explicit authority-source contract (allowed vs forbidden), - remove hidden fallbacks in `ProviderConnectionPolicy` and Provider Connection resource query scoping, - add targeted tests proving 404 vs 403 semantics and that legacy query aliases cannot grant authority. - **Explicit non-goals**: - no provider-neutral target-scope refactor (Spec 281 family), - no provider registry / OAuth redesign, - no schema changes, - no broad link/query cleanup beyond Provider Connections (defer to `canonical-link-query-cleanup` candidate). - **Permanent complexity imported**: a small set of contract tests + tightened policy/resource guardrails; no new persisted truth or abstraction layer. - **Why now**: Spec 338 tightened the workspace/environment scope contract. Provider Connections remain the main credential-adjacent hub; harden scope authority before adding more provider operations or onboarding variants. - **Why not local**: UI-only fixes do not guarantee consistent authority across create/view/actions. This must be enforced at the policy/resource boundary and guarded by tests. - **Approval class**: Core Enterprise (security / authorization hardening). - **Red flags triggered**: credential-adjacent surface + authorization semantics + cross-surface context drift. **Defense**: narrow scope, reuse existing `WorkspaceContext` + capabilities, and forbid new abstraction/persistence. - **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 1 | Komplexität: 1 | Produktnähe: 2 | Wiederverwendung: 2 | **Gesamt: 10/12** - **Decision**: approve. ## Summary Provider Connections sit at the provider boundary and are credential-adjacent. This spec hardens their authority model so that: - **Create** requires an explicit `environment_id` (no remembered-environment or Filament-tenant fallback). - **View/Edit/Actions** derive scope from the `ProviderConnection` record (workspace + managed environment ownership). - **List filtering** uses explicit `environment_id` only, validated against the current workspace and access scope. - **Hidden context** (topbar remembered environment, Filament tenant context, legacy query aliases) must not grant authority. This is a narrow P1 security hardening slice following the Spec 338 contract work, with tests that make regressions obvious. ## Spec Scope Fields *(mandatory)* - **Scope**: workspace (workspace-owned hub with optional environment filter) - **Primary Routes (representative)**: - `/admin/provider-connections` - `/admin/provider-connections?environment_id=` - `/admin/provider-connections/create?environment_id=` - `/admin/provider-connections/{record}` (view) - `/admin/provider-connections/{record}/edit` - **Data Ownership**: `ProviderConnection` is workspace-owned and is linked to one `ManagedEnvironment` via `managed_environment_id`. No schema changes. - **RBAC**: - Non-member / out-of-workspace access: 404 (deny-as-not-found) - Member but missing capability: 403 - Capabilities used by this surface: `Capabilities::PROVIDER_VIEW`, `Capabilities::PROVIDER_MANAGE`, and existing dedicated-credential capability variants. ## Authority / Scope Contract *(Spec 339)* ### A. Allowed authority sources (canonical) Create: - explicit `environment_id` query parameter (validated and workspace-owned) - explicit environment selection in form state (only if a picker is introduced later) - workspace context from `WorkspaceContext` (required) View/Edit/Actions: - `ProviderConnection.workspace_id` + `ProviderConnection.managed_environment_id` - record → tenant (`ManagedEnvironment`) → `workspace_id` (derived) - workspace context from `WorkspaceContext` for deny-as-not-found semantics and list scoping ### B. Forbidden authority sources (must never grant permission) - remembered environment (`WorkspaceContext::lastEnvironmentId` and related helpers) - Filament tenant fallback (`Filament::getTenant()`) as a source of workspace/environment authority - legacy query aliases (`tenant`, `tenant_id`, `managed_environment_id`) as authority inputs - Filament internal state/query keys as authority (`tableFilters[...]`, etc.) ### C. 404 vs 403 semantics (authorization UX contract) - Non-member / no workspace context / wrong workspace / out-of-scope record: **404** (deny-as-not-found) - Member but missing capability: **403** ## Required Runtime Decisions ### D1 — Create requires explicit `environment_id` (no hidden fallback) Decision: - Provider Connection create is only valid with explicit `environment_id` that belongs to the current workspace. - Remembered environment and Filament tenant must never substitute for missing `environment_id`. Acceptance: - `/admin/provider-connections/create` without `environment_id` cannot be used to create a connection. - Wrong-workspace `environment_id` denies as 404. - Member without `PROVIDER_MANAGE` denies as 403. ### D2 — Workspace authority requires explicit workspace context (no Filament fallback) Decision: - Workspace authority for Provider Connections must come from `WorkspaceContext` (session workspace), not from Filament tenant fallback. Acceptance: - If workspace context is missing, Provider Connections deny as 404 even if Filament tenant is set. ### D3 — Environment filtering is explicit and inert Decision: - `environment_id` may filter list results but must be validated against current workspace and the user’s environment access scope. - Legacy query keys do not grant access and are not treated as authority. Acceptance: - `?managed_environment_id=...`, `?tenant=...`, `?tenant_id=...` do not grant access or widen results. ## UI Surface Impact *(mandatory — UI-COV-001)* - [ ] No UI surface impact - [x] Existing page changed - [ ] New page/route added - [ ] Navigation changed - [ ] Filament panel/provider surface changed - [ ] New modal/drawer/wizard/action added - [ ] New table/form/state added - [ ] Customer-facing surface changed - [x] Dangerous action changed - [ ] Status/evidence/review presentation changed - [x] Workspace/environment context presentation changed ## UI/Productization Coverage *(UI-COV-001)* - **Route/page/surface**: Provider Connections (list, view, create, edit) + credential-adjacent actions (dedicated credential management and provider operations) in `ProviderConnectionResource`. - **Current page archetype**: workspace admin “Integration/Settings” hub resource (credential-adjacent). - **Design depth**: Domain Pattern Surface (security/authority hardening; minimal UX change expected). - **Repo-truth level**: repo-verified (`ProviderConnectionResource`, `ProviderConnectionPolicy`, `WorkspaceContext`, `WorkspaceHubEnvironmentFilter` + existing ProviderConnections feature tests). - **Existing pattern reused**: Spec 338 scope contract + existing deny-as-not-found semantics, `WorkspaceContext`, capabilities, and existing ProviderConnections test families. - **New pattern required**: none (tighten existing seams; avoid new abstraction/persistence). - **Screenshot required**: no (unless implementation changes visible CTA/empty-state messaging; then add one targeted screenshot in the implementation PR). - **Page audit required**: no (existing archetype). - **Customer-safe review required**: no (operator-only surface). - **Dangerous-action review required**: yes (credential-adjacent actions must remain confirmation/authorization/audit-safe and scoped to record ownership). - **Coverage files to update (in implementation PR)**: - [ ] `docs/ui-ux-enterprise-audit/route-inventory.md` (only if navigation entries/routes change; not expected) - [ ] `docs/ui-ux-enterprise-audit/design-coverage-matrix.md` (no new surface; expected `no`) - [x] `N/A - no new reachable UI surface added; authority hardening only` ## Cross-Cutting / Shared Pattern Reuse *(mandatory)* - **Cross-cutting feature?**: yes - **Interaction class(es)**: navigation + scope presentation, authorization semantics, credential-adjacent action gating. - **Systems touched (expected)**: - `apps/platform/app/Filament/Resources/ProviderConnectionResource.php` - `apps/platform/app/Policies/ProviderConnectionPolicy.php` - `apps/platform/app/Support/Workspaces/WorkspaceContext.php` - `apps/platform/app/Support/Navigation/WorkspaceHubEnvironmentFilter.php` - `apps/platform/tests/Feature/ProviderConnections/*` - **Existing pattern(s) to extend**: Spec 338 scope/authority contract (explicit `environment_id` filter, remembered environment is navigation-only, deny-as-not-found for out-of-scope). - **Allowed deviation and why**: none (tighten existing seams; no new surface frameworking). - **Consistency impact**: authority sources must match across list/create/policy/actions; tests must enforce that legacy query aliases and hidden context do not widen authority. - **Review focus**: “credential-adjacent authority comes from explicit input or record ownership only.” ## OperationRun UX Impact - **Touches OperationRun start/completion/link UX?**: no - **N/A**: This spec hardens authority sources and authorization semantics. Provider operations that already run via `OperationRun` must remain scoped to the record’s owning environment/workspace; no new `OperationRun` types or start UX changes are introduced. ## Provider Boundary / Platform Core Check - **Shared provider/platform boundary touched?**: yes - **Boundary classification**: platform-core authority (workspace/environment scope) + provider-owned identity (provider scope identifiers) must remain separated. - **Seams affected**: Provider Connection authority sources (workspace/environment), policy decisions, and filter/query semantics. - **Why this does not deepen provider coupling accidentally**: The contract uses neutral platform terms (`workspace`, `environment_id`, record ownership) and explicitly forbids conflating provider “tenant” identifiers with platform scope authority. - **Follow-up path**: none (follow-up work belongs in `canonical-link-query-cleanup` and/or Spec 281 family, not in this slice). ## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)* - **Test purpose / classification**: Feature (authorization/scope contract). - **Validation lane(s)**: fast-feedback (Feature). Browser smoke is optional and only justified if visible UX changes occur. - **Why this classification and these lanes are sufficient**: The risk is scope/authorization drift, which is best proven by targeted policy/resource tests (positive + negative cases) without introducing heavy or browser-wide cost by default. - **Planned validation commands**: - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections --filter=ScopeHardening` - `cd apps/platform && ./vendor/bin/sail pint --dirty --format agent` ## Acceptance Criteria - **AC1**: Provider Connection create requires explicit `environment_id`; remembered environment and Filament tenant cannot substitute. - **AC2**: Provider Connection list/view/edit/actions derive scope only from current workspace context + explicit `environment_id` (filter/create) or from record ownership (view/edit/actions). - **AC3**: Legacy query aliases (`tenant`, `tenant_id`, `managed_environment_id`) do not grant access or widen results. - **AC4**: 404 vs 403 semantics match the repo rules: out-of-scope/non-member is 404; missing capability is 403. - **AC5**: Targeted feature tests are green and make authority regressions obvious. ## User Scenarios & Testing *(mandatory)* ### User Story 1 — View Provider Connections safely (Priority: P1) As an operator in a workspace, I can list and view Provider Connections for environments I am entitled to, and I can optionally filter the list by an explicit environment. **Independent Test**: A user with workspace membership and `PROVIDER_VIEW` can list and view records in-scope; a non-member or wrong-workspace access is 404; a member without capability is 403; legacy query aliases do not widen access. **Acceptance Scenarios**: 1. **Given** a workspace member with `PROVIDER_VIEW` on Environment A, **When** they visit `/admin/provider-connections`, **Then** the list contains only connections in environments they can access in the current workspace. 2. **Given** the same user, **When** they visit `/admin/provider-connections?environment_id=`, **Then** the list is filtered to Environment A and remains scope-safe. 3. **Given** a user without workspace membership, **When** they hit Provider Connections routes, **Then** the response is deny-as-not-found (404). 4. **Given** a member without `PROVIDER_VIEW`, **When** they attempt to list/view, **Then** the response is 403. 5. **Given** any user, **When** they add `tenant`, `tenant_id`, or `managed_environment_id` query keys, **Then** those keys do not grant access or change authority behavior. ### User Story 2 — Create Provider Connection only with explicit target environment (Priority: P1) As an operator, I can create a Provider Connection only when I explicitly choose the target environment (by passing `environment_id`) and I am authorized for that environment. **Independent Test**: Create is impossible without explicit `environment_id`, even if a remembered environment or Filament tenant exists. **Acceptance Scenarios**: 1. **Given** a member with `PROVIDER_MANAGE` on Environment A, **When** they open `/admin/provider-connections/create?environment_id=`, **Then** create is allowed and the record is owned by Environment A + the current workspace. 2. **Given** a request to `/admin/provider-connections/create` without `environment_id`, **When** the user has a remembered environment, **Then** create is still denied (no implicit fallback). 3. **Given** an `environment_id` from another workspace, **When** the user attempts create, **Then** the response is 404. 4. **Given** a member without `PROVIDER_MANAGE`, **When** they attempt create for an in-scope environment, **Then** the response is 403. ### User Story 3 — Perform credential-adjacent actions with record-derived authority (Priority: P2) As an operator, credential-adjacent actions (dedicated credential management, provider operations) execute only when I am authorized for the record’s owning environment/workspace, independent of remembered environment or Filament tenant state. **Independent Test**: A record-bound action uses the record’s ownership and denies 404/403 correctly. **Acceptance Scenarios**: 1. **Given** a Provider Connection record owned by Environment A, **When** a user with access to Environment A triggers an action, **Then** the action is authorized using record ownership and capability checks. 2. **Given** the same record, **When** a user’s remembered environment is Environment B, **Then** authorization still uses record ownership (A), not hidden context. 3. **Given** a member without the dedicated credential capability, **When** they attempt dedicated credential actions, **Then** the response is 403. ## Follow-up spec candidates - `canonical-link-query-cleanup` (broader link/query inventory + alias cleanup after scope contract) - `environment-resource-context-follow-through` (reduce hidden Filament context reliance inside selected environment resources)