Implements platform feature branch `285-workspace-rbac-environment-access`. Summary: - switch managed environment authorization to workspace-first role resolution with explicit environment-scope narrowing - rewire Filament pages, resources, policies, and user tenant access helpers to the shared access-scope resolver - add Spec 285 coverage across unit, feature, and browser tests plus full spec artifacts Validation: - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Auth/WorkspaceFirstCapabilityResolverTest.php tests/Unit/Auth/ManagedEnvironmentAccessScopeResolverTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Auth/WorkspaceFirstManagedEnvironmentAccessTest.php tests/Feature/Filament/ManagedEnvironmentAccessScopeManagementTest.php tests/Feature/Filament/WorkspaceMembershipRoleManagementTest.php tests/Feature/Rbac/GovernanceArtifactsWorkspaceFirstAuthorizationTest.php tests/Feature/Rbac/OperationRunWorkspaceFirstAuthorizationTest.php tests/Feature/Rbac/ProviderConnectionWorkspaceFirstPolicyTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Verification/ProviderExecutionReauthorizationTest.php tests/Feature/ProviderConnections/ProviderConnectionHealthCheckStartSurfaceTest.php tests/Feature/Tenants/TenantProviderBackedActionStartTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Audit/TenantMembershipAuditLogTest.php tests/Feature/Filament/TenantMembersTest.php tests/Feature/TenantRBAC/TenantMembershipCrudTest.php tests/Feature/TenantRBAC/TenantSwitcherScopeTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php` - `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` Target branch: `platform-dev`. Follow-up integration path after merge: - `platform-dev` -> `dev`. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #344
40 KiB
Feature Specification: Workspace-first RBAC & Environment Access Scoping
Feature Branch: 285-workspace-rbac-environment-access
Created: 2026-05-09
Status: Blocked by external prerequisites
Input: User description: "Follow instructions in #prompt:SKILL.md with these arguments: mit 285 weitermachen"
Spec Candidate Check (mandatory — SPEC-GATE-001)
- Problem: Repo truth already has workspace memberships, managed-environment memberships, a workspace capability resolver, and a tenant capability resolver, but authorization still depends on two parallel role-bearing truths. Operators can be valid workspace members while environment visibility, Filament tenant selection, provider-connection access, run drilldowns, and governance surfaces still hinge on separate
ManagedEnvironmentMembershiprows and a second capability map. - Today's failure: access semantics are not consistently workspace-first.
WorkspaceContext,User::getTenants(),ProviderConnectionPolicy,OperationRunPolicy,FindingPolicy,EvidenceSnapshotPolicy,ReviewPackPolicy, andTenantReviewPolicyall mix workspace membership, environment membership, and capability checks differently. That makes deny-as-not-found behavior harder to reason about, duplicates role administration, and blocks a calm future role model such as Workspace Admin versus Customer Viewer because role truth is split before customer-safe roles even exist. - User-visible improvement: operators manage role-bearing access once at workspace level, environment visibility becomes an explicit secondary scope instead of a second hidden RBAC core, and tenant-owned pages, provider connections, run drilldowns, evidence, findings, and governance review surfaces all follow the same 404 versus 403 rules.
- Smallest enterprise-capable version: keep
workspace_membershipsas the only role-bearing access truth, replace role-bearingManagedEnvironmentMembershipsemantics with a narrow optional managed-environment access-scope overlay, retargetCapabilityResolver,User::canAccessTenant(),WorkspaceContext, and the key environment-owned policies to evaluate workspace role first and environment scope second, and migrate the current tenant-membership management surface so it no longer edits a second role family. - Explicit non-goals: no
/systemplane RBAC redesign, no full customer-portal RBAC migration, no mandatory environment-level ACL product, no per-environment role matrix, no new role-productization surface, no provider-capability registry work from Spec283, no source-taxonomy work from Spec284, no copy/localization neutralization from Spec286, no no-legacy guardrail pack from Spec287, and no compatibility shim or dual-write path. - Permanent complexity imported: one unified workspace-first access-resolution path, one narrow optional managed-environment scope overlay contract, targeted policy rewiring, a migration of the current tenant-membership UI into workspace-role management plus environment-scope management, and focused unit, feature, and browser proof. No new role family and no new independent persisted source of truth are added.
- Why now: Specs
279through283reserve the workspace-first cutover pack specifically so workspace context, provider-neutral identity, and provider capability truth do not sit on top of tenant-first authorization. Without285, the route shell and provider boundary work still rest on a dual RBAC core that keeps the product Microsoft- and tenant-shaped in its access semantics. - Why not local: a local policy patch or one-off page helper would leave
User,WorkspaceContext,CapabilityResolver,OperationRunPolicy, relation managers, and onboarding or provider surfaces inconsistent. The drift is structural and already spans models, policies, Filament navigation, and tests. - Approval class: Core Enterprise
- Red flags triggered: authorization source-of-truth change, cross-cutting policy retargeting, and semantic replacement of an existing role-bearing model. Defense: the repo already carries both workspace and managed-environment role truths. Canonical replacement is safer and narrower than keeping them synchronized through more compatibility logic.
- Score: Nutzen: 2 | Dringlichkeit: 2 | Scope: 1 | Komplexität: 1 | Produktnähe: 2 | Wiederverwendung: 2 | Gesamt: 10/12
- Decision: approve
Spec Scope Fields (mandatory)
- Scope: workspace
- Primary Routes:
/admin/workspaces/admin/workspaces/{record}/admin/provider-connections- named onboarding routes
admin.onboardingandadmin.onboarding.draft - current admin-panel managed-environment selection and tenant-owned routes used by
Finding,EvidenceSnapshot,ReviewPack,TenantReview, andOperationRundrilldowns, regardless of whether the shell is still the current tenant-context route family or the prepared workspace-first route family from Spec280 TenantResourcemembership and related-access surfaces that currently expose managed-environment membership CRUD
- Data Ownership:
workspace_membershipsremain the workspace-owned, role-bearing source of truth- the current
managed_environment_membershipspersistence becomes a narrow managed-environment access-scope overlay or is replaced in-place by its workspace-first successor; it must not remain a second role-bearing truth ManagedEnvironment,ProviderConnection,Finding,EvidenceSnapshot,ReviewPack, andTenantReviewremain managed-environment-owned records anchored byworkspace_idplusmanaged_environment_idOperationRuncontinues to support both workspace-bound records anchored byworkspace_idalone and managed-environment-bound records anchored byworkspace_idplus optionalmanaged_environment_id
- RBAC:
- workspace membership is the first entitlement boundary
- managed-environment access scope is an optional narrowing boundary, not a second independent role authority
- capability checks for managed-environment-owned resources continue to use the existing capability registry, but role resolution must come from workspace membership rather than environment membership
- non-members or out-of-scope actors stay
404; in-scope members missing a required capability stay403
Cross-Cutting / Shared Pattern Reuse
- Cross-cutting feature?: yes
- Interaction class(es): navigation entry points, context selection, relation-manager membership management, cross-resource authorization, run drilldowns, and shared policy enforcement
- Systems touched:
WorkspaceContext,User::managedEnvironments(),User::getTenants(),User::canAccessTenant(),WorkspaceCapabilityResolver,CapabilityResolver,TenantMembershipManager,WorkspaceMembershipManager,OperationRunCapabilityResolver, key environment-owned policies,ManageTenantMemberships,TenantMembershipsRelationManager, workspace membership relation managers, and sharedUiEnforcementpaths that depend on capability outcomes - Existing pattern(s) to extend: workspace membership resolution, current capability resolver caching, shared deny-as-not-found policy pattern, existing workspace membership management, and existing action-surface enforcement for membership CRUD
- Shared contract / presenter / builder / renderer to reuse:
WorkspaceCapabilityResolver,CapabilityResolveras the current managed-environment capability entry point,WorkspaceContext,OperationRunCapabilityResolver,UiEnforcement,WorkspaceMembershipManager, and the existing Filament relation-manager contract family - Why the existing shared path is sufficient or insufficient: the repo already has the right core seams, but they currently stop at workspace-only or environment-only access decisions. The feature should converge them into one shared workspace-first access contract instead of introducing a third resolver stack.
- Allowed deviation and why: none. Any temporary compatibility alias, duplicate manager, or page-local access resolver would extend the drift this slice is meant to remove.
- Consistency impact: workspace chooser, managed-environment selection, provider-connections access, environment-owned resource policies, and membership-management surfaces must all derive access from the same workspace-first contract before provider capability or operability checks add deeper gating.
- Review focus: reviewers must verify that no role-bearing
ManagedEnvironmentMembershippath survives in policies or Filament gates, that workspace membership becomes the only role authority, and that environment scope is modeled as narrowing rather than as a second full RBAC system.
OperationRun UX Impact
- Touches OperationRun start/completion/link UX?: yes
- Shared OperationRun UX contract/layer reused:
OperationRunCapabilityResolver,OperationRunPolicy,ProviderOperationStartGate, and the existingOperationRunServicelifecycle path - Delegated start/completion UX behaviors: run viewability, required capability lookups, workspace-versus-environment entitlement resolution, and provider-capability follow-through stay delegated to the shared run authorization seams
- Local surface-owned behavior that remains: start surfaces keep only initiation inputs and the existing
Open operationor drilldown affordances; this slice changes the access contract they rely on - Queued DB-notification policy:
N/A - Terminal notification path: existing central lifecycle mechanism
- Exception required?: none
Provider Boundary / Platform Core Check
N/A - no shared provider/platform boundary is redefined in this slice. The feature consumes provider-neutral capability outcomes from Spec 283 as downstream inputs only.
UI / Surface Guardrail Impact
| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / N/A Note |
|---|---|---|---|---|---|---|
| Workspace membership management | yes | Native Filament resource relation manager | role-bearing membership CRUD, audit-safe owner guards | page, relation manager, modal | no | becomes the only role-management surface |
| Managed-environment access-scope management (retargeted from current tenant membership surface) | yes | Native Filament relation manager plus existing page shell | optional environment narrowing, selection visibility, drilldown continuity | page, relation manager, modal | no | must stop acting like a second role editor |
| Shared managed-environment selection and tenant-owned page access | yes | Mixed shared shell plus existing native pages | context selection, 404/403 semantics, route continuity, run drilldowns | shell, page, remembered context, URL-query | no | workflow guardrail change more than layout change |
Decision-First Surface Role
| 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 |
|---|---|---|---|---|---|---|---|
| Workspace membership management | Primary Decision Surface | Decide which workspace role a user should hold | member, role, and last-owner guard status | audit trail and historical change detail | Primary because this is the canonical role-bearing authority after the cutover | Matches workspace-first SaaS administration | removes duplicate role assignment across workspace and environment surfaces |
| Managed-environment access-scope management | Secondary Context Surface | Decide whether a workspace member should be narrowed to a subset of environments | whether access inherits workspace-wide or is explicitly narrowed | exact scoped environments and audit history | Secondary because it narrows visibility after the main workspace role decision | Keeps environment scope subordinate to workspace role truth | avoids forcing operators to reason about two different role systems |
| Shared managed-environment selection and tenant-owned page access | Primary Decision Surface | Decide whether the current environment is accessible and what next page can be opened safely | current workspace, selected environment, access-denied routing, one next valid destination | deeper diagnostics, provider capability blockers, and operability detail | Primary because operators feel the cutover first through context selection and page access | Follows the existing admin shell and drilldown workflow | reduces surprise 404/403 drift between pages and runs |
Audience-Aware Disclosure
| 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 |
|---|---|---|---|---|---|---|---|
| Workspace membership management | operator-MSP, support-platform | member, role, allowed management actions, owner-guard status | audit detail, membership source, history | raw model identifiers | Change role or Add member |
raw IDs and low-level audit context stay secondary | workspace role is shown once as the canonical authority |
| Managed-environment access-scope management | operator-MSP, support-platform | whether access inherits workspace-wide or is explicitly scoped, plus the scoped environments | who is scoped where, why, and audit history | raw pivot identifiers and debug metadata | Manage access scope |
raw pivot data stays hidden | the page explains environment narrowing once and does not repeat workspace role meaning |
| Shared managed-environment selection and tenant-owned page access | operator-MSP, support-platform | selected workspace, selected environment, and whether access is allowed | scoped-access reasons, provider capability or operability follow-up | raw policy internals and debug payloads | Open environment or Return to workspace |
debug semantics stay out of default-visible UI | access denial is explained through one shared contract before deeper diagnostics appear |
UI/UX Surface Classification
| 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Workspace membership management | List / Detail / Settings | CRUD / Relation-first Resource | add member or change role | inline relation-manager management | forbidden | grouped relation actions only | grouped destructive actions with confirmation | workspace detail memberships relation | same workspace detail page | workspace context | Workspace member | canonical role-bearing access | none |
| Managed-environment access-scope management | List / Detail / Settings | CRUD / Relation-first Resource | add or remove allowed environments for a workspace member | inline relation-manager or dedicated scoped page | forbidden | grouped relation actions only | grouped destructive actions with confirmation | managed-environment access scope page under current workspace or environment | same access-scope page | workspace and managed-environment context | Environment access scope | whether access is inherited or narrowed | none |
| Shared managed-environment selection and tenant-owned page access | Navigation / Drilldown / Context | Global-context Shell | open the current environment safely | explicit environment selection and deep-link entry points | required where the shell already uses row or identifier click | secondary navigation or diagnostics in helper placements | none introduced by this slice | existing admin shell and context chooser | existing tenant-owned page routes and run drilldowns | workspace, managed environment, lifecycle | Managed environment | access allowed or deny-as-not-found boundary | none |
Operator Surface Contract
| 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 |
|---|---|---|---|---|---|---|---|---|---|---|
| Workspace membership management | Workspace owner or manager | decide which workspace role a member should hold | relation-manager settings surface | What should this member be allowed to do across the workspace? | member, workspace role, owner-guard state | audit history and membership source | role authority, owner-guard | TenantPilot only | Add member, Change role | Remove member |
| Managed-environment access-scope management | Workspace owner or manager | decide whether a member should be limited to specific environments | relation-manager or dedicated scoped page | Should this member inherit workspace-wide access or be narrowed to specific environments? | inheritance mode, selected environments | audit history, scope source | inherited versus narrowed | TenantPilot only | Add allowed environment, Remove allowed environment | Clear or narrow access scope |
| Shared managed-environment selection and tenant-owned page access | Workspace operator | decide whether the current environment or run can be opened safely | global-context shell and page access | Can I open this environment, run, or resource from the current workspace? | current workspace, selected environment, and access result | deeper operability or provider blockers | workspace entitlement, environment scope, capability, operability | none | Open environment, Return to workspace | none |
Proportionality Review
- New source of truth?: yes, but it is a canonical replacement of the existing role-bearing source-of-truth split rather than a new persisted truth
- New persisted entity/table/artifact?: no
- New abstraction?: yes
- New enum/state/reason family?: no
- New cross-domain UI framework/taxonomy?: no
- Current operator problem: access control is duplicated across workspace roles and managed-environment memberships, which creates inconsistent page access, duplicated administration, and future-role drift.
- Existing structure is insufficient because: the current structure requires two role-bearing membership tables plus two resolver stacks to answer one authorization question. That contradicts the workspace-first cutover and makes deny-as-not-found rules inconsistent.
- Narrowest correct implementation: keep workspace memberships as the only role-bearing truth, reinterpret or replace the existing managed-environment membership model as a narrow access-scope overlay, and retarget the existing shared resolvers, policies, and relation managers instead of adding a new RBAC framework.
- Ownership cost: capability resolution, model semantics, membership management surfaces, policy tests, and browser smoke all need synchronized updates; that cost is bounded because the feature removes a duplicate model instead of adding a third one.
- Alternative intentionally rejected: keeping both role-bearing tables and adding synchronization or fallback logic. That would preserve the split truth and add more compatibility complexity in a pre-production codebase.
- Release truth: current-release truth
Compatibility posture
This feature assumes a pre-production environment.
Backward compatibility, legacy aliases, dual-write logic, historical-fixture preservation, and compatibility-specific tests are out of scope unless an adjacent prerequisite spec explicitly requires them.
Canonical replacement is preferred over preservation.
External implementation prerequisites
- Spec
280must already provide the workspace-first route and panel-shell baseline on the implementation branch before285runtime work begins. - Spec
281must already provide the provider-neutral target-scope and provider-identity baseline so access scope does not hardcode Microsoft-specific scope contracts. - Spec
283must already provide the provider capability context consumed by provider-backed actions once workspace-first access passes. - Spec
284remains adjacent but is not a hard blocker for the RBAC cutover as long as285does not absorb source-taxonomy work.
Testing / Lane / Runtime Impact
- Test purpose / classification: Unit, Feature, Browser
- Validation lane(s): fast-feedback, confidence, browser
- Why this classification and these lanes are sufficient: the resolver and scope-overlay logic need unit proof; policy, Filament access, and environment-selection behavior need feature proof; one browser smoke is required to confirm that workspace role management plus environment-scope management drive real shell access consistently.
- New or expanded test families: one unit family for workspace-first access resolution and scoped-environment inheritance, one feature family for environment-owned policy retargeting, one feature family for membership-surface migration, one feature family for
OperationRunaccess continuity, and one narrow browser smoke for workspace role plus environment-scope behavior - Fixture / helper cost impact: moderate because proof needs workspace membership, optional environment-scope records, managed-environment context, and representative provider/run resources without widening global test defaults
- Heavy-family visibility / justification: one browser smoke only; no heavy-governance family is justified
- Special surface test profile: standard-native-filament, global-context-shell, shared-detail-family
- Standard-native relief or required special coverage: standard Filament feature coverage is sufficient for membership management surfaces; one global-context-shell smoke is required for environment selection and page access continuity
- Reviewer handoff: reviewers must verify that
workspace_membershipsbecome the sole role-bearing truth, that any surviving managed-environment overlay no longer carries capability authority, thatProviderConnectionResourcestays non-globally-searchable with View and Edit pages intact, that touched destructive membership actions still use->action(...)plus->requiresConfirmation(), that Filament remains v5 on Livewire v4, that provider registration stays inapps/platform/bootstrap/providers.php, and that the planned tests cover both positive and negative access paths - Budget / baseline / trend impact: moderate feature-local increase only
- Escalation needed:
document-in-featureif implementation must stage a temporary scope-overlay alias;reject-or-splitif it keeps dual role authority - Active feature PR close-out entry: Guardrail
- Planned validation commands:
export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Unit/Auth/WorkspaceFirstCapabilityResolverTest.php tests/Unit/Auth/ManagedEnvironmentAccessScopeResolverTest.php)export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Feature/Auth/WorkspaceFirstManagedEnvironmentAccessTest.php tests/Feature/Rbac/ProviderConnectionWorkspaceFirstPolicyTest.php tests/Feature/Rbac/OperationRunWorkspaceFirstAuthorizationTest.php tests/Feature/Rbac/GovernanceArtifactsWorkspaceFirstAuthorizationTest.php tests/Feature/Filament/WorkspaceMembershipRoleManagementTest.php tests/Feature/Filament/ManagedEnvironmentAccessScopeManagementTest.php)export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail artisan test --compact tests/Browser/Spec285WorkspaceRbacEnvironmentAccessSmokeTest.php)export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && REPO_ROOT="$(git rev-parse --show-toplevel)" && (cd "$REPO_ROOT/apps/platform" && ./vendor/bin/sail bin pint --dirty --format agent)
Candidate Selection Gate Summary
- Selected candidate:
285 - Workspace-first RBAC & Environment Access Scoping - Source locations:
docs/product/spec-candidates.mdunder the reserved workspace-first cutover packdocs/product/roadmap.mdunder the same cutover ordering
- Why selected now: the user explicitly requested the reserved slot
285, and repo truth shows this is the next unspecced cutover gap after workspace context, provider-neutral target scope, and provider capability preparation. The remaining blocker is no longer route or provider vocabulary alone, but the dual authorization core. - Why close alternatives were deferred:
284remains source-taxonomy work and should not be folded into RBAC replacement286should follow once the canonical access model and environment-scope semantics are stable287should harden the cutover after285finishes replacing the dual access core instead of before
- Smallest viable implementation slice: replace role-bearing managed-environment membership truth with workspace-first role resolution plus an optional environment-scope overlay, then retarget the key policies and membership surfaces around that contract
- Documented deviations from raw candidate wording:
- the raw candidate says
TenantMembershipshould be removed or replaced, but current repo truth usesManagedEnvironmentMembershiprather thanTenantMembership - repo truth already has
WorkspaceMembership,WorkspaceCapabilityResolver, and workspace-level role management, so285is not greenfield RBAC; it is a consolidation cutover - the optional environment-scope layer should be modeled as a narrow visibility overlay, not as another full role-bearing ACL system
- the raw candidate says
Completed-Spec Guardrail Result
specs/279-workspace-managed-environment-core/already exists with implementation-close-out history and remains historical prerequisite context onlyspecs/280-workspace-tenancy-environment-routing/already exists withStatus: Readyand remains adjacent prepared context onlyspecs/281-provider-connection-scope/already exists withStatus: Readyand remains adjacent prepared context onlyspecs/282-governance-artifact-retargeting/already exists with implementation-close-out history and remains historical adjacent context onlyspecs/283-provider-capability-registry/already exists withStatus: Readyand remains adjacent prepared context onlyspecs/062-tenant-rbac-v1/andspecs/065-tenant-rbac-v1/remain historical tenant-first RBAC context and are not modified by this package- the target package
specs/285-workspace-rbac-environment-access/did not exist before this prep run and is the sole new package created here
Deferred Adjacent Candidates
284 - Provider-neutral Artifact Source Taxonomy v1286 - UI Copy, IA & Localization Neutralization287 - Cutover Quality Gates & No-Legacy Enforcement
User Scenarios & Testing
User Story 1 - Workspace membership becomes the only role authority (Priority: P1)
As a workspace owner or manager, I want one canonical role-bearing membership record so every managed-environment page and run drilldown inside my workspace follows the same role decision.
Why this priority: this is the core cutover value. Without it, environment-owned pages still depend on a second RBAC truth.
Independent Test: create one workspace member without any explicit managed-environment scope rows, open an entitled managed environment, and confirm provider connections, findings, evidence, review packs, and run drilldowns all use the workspace role consistently.
Acceptance Scenarios:
- Given a user has a workspace membership with the required role and no environment-scope narrowing, When they open a managed-environment-owned resource inside that workspace, Then access is decided from workspace membership plus capability and does not require a second role-bearing environment membership.
- Given a user is not a workspace member, When they open any managed-environment-owned route or run drilldown for that workspace, Then the system responds as deny-as-not-found.
User Story 2 - Environment scope can narrow visibility without becoming a second RBAC system (Priority: P1)
As a workspace owner or manager, I want to optionally narrow a member to specific managed environments without creating a second role matrix per environment.
Why this priority: the roadmap explicitly wants environment access scopes to stay feasible, but not at the cost of reintroducing another full ACL core.
Independent Test: grant one workspace member access to only one environment, confirm that environment is selectable and a sibling environment in the same workspace stays hidden or 404, while the same workspace role capabilities apply inside the allowed environment.
Acceptance Scenarios:
- Given a workspace member has an explicit allowlist for one managed environment, When they try to open another managed environment in the same workspace, Then the system responds as deny-as-not-found.
- Given a workspace member has an explicit allowlist for one managed environment, When they open an allowed environment, Then their role capabilities are derived from workspace membership and only visibility is narrowed by environment scope.
User Story 3 - Membership management surfaces stop editing duplicate role truth (Priority: P2)
As a workspace owner or manager, I want workspace roles and environment scopes managed on purpose-built surfaces so I do not assign contradictory roles in two places.
Why this priority: the current tenant-membership UI encodes the drift directly; until it is retargeted, operators can keep rebuilding the dual model.
Independent Test: open the workspace membership surface and the retargeted environment-scope surface, confirm roles are managed only at workspace level, confirm environment surfaces only manage visibility scope, and confirm both mutation paths audit their changes.
Acceptance Scenarios:
- Given a workspace owner opens membership management, When they change a member role, Then the role change is stored and audited at workspace scope only.
- Given a workspace owner opens the managed-environment access-scope surface, When they add or remove one environment from scope, Then the mutation changes environment visibility only and does not expose a second role selector.
Edge Cases
- What happens when a workspace member has no explicit environment-scope rows and the workspace contains archived or removed managed environments?
- How does the system handle a remembered managed-environment context after a scope row is removed or a workspace membership is deleted?
- What happens when an
OperationRunbelongs to a workspace with nomanaged_environment_idversus one that is environment-bound? - How does the system handle last-owner protection when workspace role management replaces the current managed-environment owner semantics?
- What happens when local RBAC passes but provider capability from Spec
283blocks the action afterward?
Requirements
Functional Requirements
- FR-001 Workspace membership is the only role-bearing authorization truth. Managed-environment-owned resource capabilities MUST resolve from workspace membership, not from a second environment role record.
- FR-002 Workspace membership is the first entitlement boundary. Non-members of the current workspace MUST receive deny-as-not-found for workspace-owned and managed-environment-owned routes, actions, and run drilldowns.
- FR-003 Managed-environment scope is an optional narrowing layer. The managed-environment overlay MUST only answer whether the current workspace member may access a specific managed environment; it MUST NOT become a second role or capability registry.
- FR-004 Full workspace inheritance is the default. If no explicit managed-environment scope narrowing exists for a workspace member, that member inherits environment visibility across the selectable managed environments in the workspace.
- FR-005 Scoped access is explicit. If explicit managed-environment scope rows exist for a workspace member, only those environments are visible or openable for that member.
- FR-006 Capability resolution stays capability-first. The existing capability registry remains the only capability vocabulary, and environment-owned policies MUST continue to check capabilities rather than raw role strings.
- FR-007
User::canAccessTenant(), Filament tenant selection, remembered tenant context, and related-context navigation MUST use the same workspace-first access contract. - FR-008 Environment-owned policies MUST be retargeted.
ManagedEnvironment,ProviderConnection,OperationRun,Finding,EvidenceSnapshot,ReviewPack,TenantReview, and other governance-review or evidence surfaces in scope MUST evaluate workspace membership first, managed-environment scope second, and capability third. - FR-009
OperationRunauthorization MUST preserve mixed workspace and managed-environment behavior. Workspace-wide runs continue to authorize against workspace membership; environment-bound runs additionally respect managed-environment scope before capability evaluation. - FR-010 Membership-management surfaces MUST split concerns. Workspace membership management remains the only role-editing surface. The current tenant-membership surface MUST either be removed or transformed into environment access-scope management with no second role selector.
- FR-011 Membership and scope mutations remain audited. Workspace role changes, environment-scope changes, and last-owner blocks MUST write canonical audit records with no secrets.
- FR-012 404 versus 403 semantics stay explicit. Non-membership or out-of-scope access returns
404; in-scope members missing the capability return403. - FR-013 Global search and shell context stay safe. Non-members or out-of-scope actors MUST not receive managed-environment hints through global search or remembered tenant context.
- FR-014 No compatibility path is introduced. The runtime MUST not keep dual-write, fallback-role reads, or legacy role-based
ManagedEnvironmentMembershipchecks once the cutover lands.
Non-Functional Requirements
- NFR-001 Resolver performance remains request-local and cacheable. The new access-resolution path MUST avoid N+1 membership or scope queries in page tables, run lists, and bulk authorization preflight.
- NFR-002 Test breadth stays bounded. Proof focuses on unit resolver behavior, feature authorization behavior, and one browser smoke; no broad browser matrix is justified.
- NFR-003 Workspace and managed-environment isolation remain explicit and diagnosable. Audit logs and denied-access logs MUST make the failed boundary diagnosable without leaking raw provider detail.
- NFR-004 Filament v5 and Livewire v4 remain unchanged. The feature MUST not introduce view publishing, custom action surfaces outside existing patterns, or asset strategy changes.
Scope Boundaries (required for this slice)
In Scope
- replacing environment role authority with workspace-first role authority
- modeling explicit managed-environment scope as narrowing only
- retargeting
CapabilityResolver,User::canAccessTenant(),WorkspaceContext, and the key environment-owned policies to that contract - migrating the current tenant-membership management surface so it no longer edits a second role family
- preserving canonical 404 versus 403 semantics across workspace-owned and environment-owned surfaces
- preserving provider-capability follow-through as a downstream gate rather than re-encoding provider rules here
Non-Goals
- designing a new customer-facing role catalog
- shipping per-environment role overrides or a full environment ACL matrix
- redefining
/systemplatform RBAC - absorbing provider capability, source taxonomy, copy/localization, or no-legacy guardrail work from Specs
283,284,286, or287 - shipping compatibility aliases or legacy dual-write logic
Assumptions
- Specs
280,281, and283will land or already be available on the eventual implementation branch before runtime work on285starts. WorkspaceMembershipandWorkspaceCapabilityResolverremain the correct extension points for canonical role-bearing access.- The current
managed_environment_membershipspersistence can be repurposed or replaced in place because the product is still pre-production. - The existing capability vocabulary in
App\Support\Auth\Capabilitiesremains valid;285changes who resolves roles, not the capability names themselves. ProviderConnectionResourceremains non-globally-searchable, while resources already carrying valid search destinations retain those destinations.
Risks
- dual-role checks may survive in one or more policies or Filament helpers and silently preserve the old model
- environment-scope management could accidentally keep role selectors or owner semantics and reintroduce a second RBAC core under a new label
OperationRunaccess and remembered tenant context may drift if they do not adopt the same workspace-first access contract as page policies- enum or capability-map convergence could widen unexpectedly if implementation tries to solve adjacent role-productization concerns in the same slice
UI Action Matrix (mandatory when Filament is changed)
Action Surface Contract satisfied?: yes
UI-FIL-001 satisfied?: yes. The slice keeps native Filament relation managers, native confirmation modals, and existing shared enforcement helpers. No local Blade replacement or asset exception is planned.
UX-001 satisfied?: yes for the touched surfaces in scope. The feature reuses existing resource and relation-manager shells rather than introducing custom Create/Edit/View layouts.
| Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions |
|---|---|---|---|---|---|---|---|---|---|---|
| Workspace membership management | apps/platform/app/Filament/Resources/Workspaces/RelationManagers/WorkspaceMembershipsRelationManager.php |
Add member |
Inline relation-manager row focus inside the workspace detail page; no redundant View action |
Change role, Remove member |
none | Add member |
none | native modal submit and cancel | yes | Remove member stays destructive, confirmation-protected, server-authorized, and last-owner-safe |
| Managed-environment access-scope management | apps/platform/app/Filament/Resources/TenantResource/Pages/ManageTenantMemberships.php plus TenantMembershipsRelationManager.php |
Add allowed environment |
Inline relation-manager or page-local scoped list; no separate inspect route | Edit scope, Remove access |
none | Add allowed environment |
none | native modal submit and cancel | yes | The surface may not expose a role selector, owner semantics, or a second role-bearing mutation path |
Key Entities
- WorkspaceMembership: existing workspace-owned, role-bearing membership pivot that remains the only source for role and capability derivation.
- ManagedEnvironmentAccessScope: logical successor to the current managed-environment membership semantics; stores optional visibility narrowing only and never grants capabilities by itself.
- ManagedEnvironment authorization decision: derived, non-persisted access payload used by
User,WorkspaceContext, policies, and run drilldowns to evaluate membership, scope, capability, and denial outcome consistently.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: In the bounded proof suite, a workspace member with the required workspace role and no explicit scope rows can open at least one provider-connection surface and one governance-artifact surface in the workspace without any role-bearing managed-environment membership row.
- SC-002: In the bounded proof suite, a workspace member narrowed to one managed environment receives
404for a sibling environment in the same workspace while still receiving the expected capability outcome inside the allowed environment. - SC-003: The retargeted membership surfaces expose exactly one role-editing plane at workspace scope and zero role selectors on the managed-environment scope surface, while every destructive membership or scope mutation remains confirmation-protected.
- SC-004: The named unit, feature, browser, and dirty-file formatting commands in this package pass without widening the proof family beyond the files listed in
spec.md,plan.md,quickstart.md, andtasks.md.