TenantAtlas/specs/285-workspace-rbac-environment-access/spec.md
ahmido c7b38606a9 feat: implement spec 285 workspace-first environment access (#344)
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
2026-05-09 12:40:50 +00:00

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 ManagedEnvironmentMembership rows and a second capability map.
  • Today's failure: access semantics are not consistently workspace-first. WorkspaceContext, User::getTenants(), ProviderConnectionPolicy, OperationRunPolicy, FindingPolicy, EvidenceSnapshotPolicy, ReviewPackPolicy, and TenantReviewPolicy all 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_memberships as the only role-bearing access truth, replace role-bearing ManagedEnvironmentMembership semantics with a narrow optional managed-environment access-scope overlay, retarget CapabilityResolver, 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 /system plane 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 Spec 283, no source-taxonomy work from Spec 284, no copy/localization neutralization from Spec 286, no no-legacy guardrail pack from Spec 287, 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 279 through 283 reserve 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. Without 285, 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.onboarding and admin.onboarding.draft
    • current admin-panel managed-environment selection and tenant-owned routes used by Finding, EvidenceSnapshot, ReviewPack, TenantReview, and OperationRun drilldowns, regardless of whether the shell is still the current tenant-context route family or the prepared workspace-first route family from Spec 280
    • TenantResource membership and related-access surfaces that currently expose managed-environment membership CRUD
  • Data Ownership:
    • workspace_memberships remain the workspace-owned, role-bearing source of truth
    • the current managed_environment_memberships persistence 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, and TenantReview remain managed-environment-owned records anchored by workspace_id plus managed_environment_id
    • OperationRun continues to support both workspace-bound records anchored by workspace_id alone and managed-environment-bound records anchored by workspace_id plus optional managed_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 stay 403

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 shared UiEnforcement paths 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, CapabilityResolver as 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 ManagedEnvironmentMembership path 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 existing OperationRunService lifecycle 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 operation or 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 280 must already provide the workspace-first route and panel-shell baseline on the implementation branch before 285 runtime work begins.
  • Spec 281 must already provide the provider-neutral target-scope and provider-identity baseline so access scope does not hardcode Microsoft-specific scope contracts.
  • Spec 283 must already provide the provider capability context consumed by provider-backed actions once workspace-first access passes.
  • Spec 284 remains adjacent but is not a hard blocker for the RBAC cutover as long as 285 does 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 OperationRun access 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_memberships become the sole role-bearing truth, that any surviving managed-environment overlay no longer carries capability authority, that ProviderConnectionResource stays 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 in apps/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-feature if implementation must stage a temporary scope-overlay alias; reject-or-split if 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.md under the reserved workspace-first cutover pack
    • docs/product/roadmap.md under 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:
    • 284 remains source-taxonomy work and should not be folded into RBAC replacement
    • 286 should follow once the canonical access model and environment-scope semantics are stable
    • 287 should harden the cutover after 285 finishes 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 TenantMembership should be removed or replaced, but current repo truth uses ManagedEnvironmentMembership rather than TenantMembership
    • repo truth already has WorkspaceMembership, WorkspaceCapabilityResolver, and workspace-level role management, so 285 is 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

Completed-Spec Guardrail Result

  • specs/279-workspace-managed-environment-core/ already exists with implementation-close-out history and remains historical prerequisite context only
  • specs/280-workspace-tenancy-environment-routing/ already exists with Status: Ready and remains adjacent prepared context only
  • specs/281-provider-connection-scope/ already exists with Status: Ready and remains adjacent prepared context only
  • specs/282-governance-artifact-retargeting/ already exists with implementation-close-out history and remains historical adjacent context only
  • specs/283-provider-capability-registry/ already exists with Status: Ready and remains adjacent prepared context only
  • specs/062-tenant-rbac-v1/ and specs/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 v1
  • 286 - UI Copy, IA & Localization Neutralization
  • 287 - 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:

  1. 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.
  2. 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:

  1. 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.
  2. 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:

  1. 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.
  2. 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 OperationRun belongs to a workspace with no managed_environment_id versus 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 283 blocks 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 OperationRun authorization 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 return 403.
  • 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 ManagedEnvironmentMembership checks 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 /system platform RBAC
  • absorbing provider capability, source taxonomy, copy/localization, or no-legacy guardrail work from Specs 283, 284, 286, or 287
  • shipping compatibility aliases or legacy dual-write logic

Assumptions

  • Specs 280, 281, and 283 will land or already be available on the eventual implementation branch before runtime work on 285 starts.
  • WorkspaceMembership and WorkspaceCapabilityResolver remain the correct extension points for canonical role-bearing access.
  • The current managed_environment_memberships persistence can be repurposed or replaced in place because the product is still pre-production.
  • The existing capability vocabulary in App\Support\Auth\Capabilities remains valid; 285 changes who resolves roles, not the capability names themselves.
  • ProviderConnectionResource remains 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
  • OperationRun access 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 404 for 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, and tasks.md.