## Summary - hard-cut environment-owned CTA links into workspace hubs to canonical `environment_id` filters - add shared workspace-hub environment filter resolution and visible filtered-state rendering across in-scope hubs - update workspace hub pages, link helpers, and focused test coverage for explicit environment CTA filtering ## Validation - Not run in this workflow Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #370
21 KiB
Implementation Plan: Environment CTA Explicit Filter Contract
Branch: 315-environment-cta-explicit-filter-contract | Date: 2026-05-16 | Spec: spec.md
Input: Feature specification from /specs/315-environment-cta-explicit-filter-contract/spec.md
Preparation status: Specification artifacts only. No runtime implementation has been performed by this preparation step.
Summary
Spec 315 hard-cuts Environment-owned CTA links into workspace hubs to one canonical explicit filter contract:
Environment Dashboard / Environment-owned CTA -> Workspace Hub ?environment_id={managed_environment_id}
Workspace hubs remain workspace-scoped. environment_id is resolved only inside the selected Workspace, represented as a visible page-level Environment filter chip, applied to data where valid, and removable through a clean hub URL. Legacy query keys such as tenant, tenant_id, managed_environment_id, tenant_scope, environment, and tableFilters are not accepted as valid Environment CTA filter state.
Technical Context
Language/Version: PHP 8.4.15, Laravel 12.52.0
Primary Dependencies: Filament 5.2.1, Livewire 4.1.4, Laravel Sail, Laravel Socialite, Laravel MCP
Storage: PostgreSQL; no schema changes for this spec
Testing: Pest 4.3.1 / PHPUnit 12.4.4; focused browser smoke where applicable
Validation Lanes: fast-feedback, confidence for existing related coverage, browser for focused rendered-state verification
Target Platform: Laravel admin application under apps/platform, local development through Sail, staging/production through Dokploy
Project Type: Web application, Laravel/Filament admin panel
Performance Goals: No material performance change. Environment filter resolution is one workspace-scoped Managed Environment lookup per relevant request/page mount and normal query narrowing thereafter.
Constraints: No migrations, seeders, new packages, env vars, queues, scheduler, or storage changes. No backward compatibility layer or dual-param support.
Scale/Scope: Cross-cutting runtime contract across critical workspace hubs and shared Environment-owned CTA/link helpers.
UI / Surface Guardrail Plan
- Guardrail scope: Changed operator-facing surfaces for visible Environment filter state and CTA query contracts.
- Native vs custom classification summary: Native Filament/Livewire pages/resources with a shared Blade partial or existing shared primitive for a small chip. No redesign.
- Shared-family relevance: Navigation entry points, dashboard CTAs, scope signals, filter summaries, and clean clear links.
- State layers in scope: URL query, page state, table/list query state, rendered page header/filter chip, shell context. Full persisted/session/deferred clear semantics are deferred to Spec 316.
- Audience modes in scope: Operator-MSP and support-platform. Customer-read-only applies only where Customer Review Workspace currently exposes a customer-safe workspace surface.
- Decision/diagnostic/raw hierarchy plan: Filter truth is default-visible; existing diagnostics/details remain on demand.
- Raw/support gating plan: No raw evidence exposure changes.
- One-primary-action / duplicate-truth control: The visible Environment filter chip is the single page-level truth for explicit Environment filter state; shell remains Workspace.
- Handling modes by drift class or surface: Hard-stop for legacy alias acceptance in Environment CTA filter behavior; review-mandatory for any page-specific exception.
- Repository-signal treatment: Contract tests and browser screenshots are required evidence.
- Special surface test profiles: global-context-shell, monitoring-state-page, standard-native-filament.
- Required tests or manual smoke: Functional-core URL/filter tests, state-contract tests, sidebar regression tests, and focused browser smoke.
- Exception path and spread control: Any page discovered not to be workspace-owned or Environment-filterable must be documented as excluded instead of receiving a partial local exception.
- Active feature PR close-out entry: Guardrail and Smoke Coverage.
Shared Pattern & System Fit
- Cross-cutting feature marker: yes.
- Systems touched:
WorkspaceHubRegistry,WorkspaceSidebarNavigation,ManagedEnvironmentLinks,OperationRunLinks,CanonicalAdminTenantFilterStateonly if immediate clean-entry correctness requires it, Filament pages/resources for critical hubs, shared views underresources/views/filament, and related tests. - Shared abstractions reused:
WorkspaceHubRegistryfor known hub paths and clean hub URL generation; existing page/table query patterns; existing workspace and Managed Environment models/factories. - New abstraction introduced? why?: A narrow
WorkspaceHubEnvironmentFilterresolver/helper is expected because multiple pages currently parse different keys and identifier types. - Why the existing abstraction was sufficient or insufficient: Spec 314 registry is sufficient for clean sidebar/global entry but intentionally treats Environment-like query params as forbidden for that clean-entry contract. Spec 315 needs a separate valid explicit CTA filter path.
- Bounded deviation / spread control: The resolver reads only
environment_id, validates only inside current Workspace, and does not become a shell/context manager or compatibility adapter.
OperationRun UX Impact
- Touches OperationRun start/completion/link UX?: yes, link semantics only.
- Central contract reused: Existing
OperationRunLinksURL helper. - Delegated UX behaviors: Tenant/workspace-safe URL resolution for OperationRun links into workspace hubs.
- Surface-owned behavior kept local: OperationRun start, completion, status, notifications, and artifact behavior remain unchanged.
- Queued DB-notification policy: N/A.
- Terminal notification path: N/A.
- Exception path: none.
Provider Boundary & Portability Fit
- Shared provider/platform boundary touched?: yes.
- Provider-owned seams: Existing provider connection data and provider external tenant IDs stay provider-adjacent model data.
- Platform-core seams: Workspace hub query contract, visible scope wording, Environment filter resolver, and CTA URL generation.
- Neutral platform terms / contracts preserved:
Workspace,Environment,Environment filter,environment_id. - Retained provider-specific semantics and why: Existing Microsoft/Intune provider connection internals remain as-is. They must not become the CTA filter identifier.
- Bounded extraction or follow-up path: Broader tenant/environment naming cleanup is deferred to Spec 317.
Constitution Check
GATE: Must pass before implementation. Re-check after runtime changes.
- Inventory-first: no Graph inventory semantics change.
- Read/write separation: no Graph writes or destructive operations are added.
- Graph contract path: no Graph calls are introduced.
- Deterministic capabilities: existing capability checks remain; new tests must assert workspace/environment access boundaries.
- RBAC-UX: workspace hub authorization remains workspace/capability-based. Cross-workspace Environment IDs are 404/safe no-access; member-missing-capability remains existing 403 behavior.
- Workspace isolation: Environment filter resolution is explicitly scoped to current Workspace and must not switch workspace.
- Tenant isolation: no cross-Workspace/Environment leakage; no provider external tenant ID lookup for CTA filter state.
- Run observability: no new OperationRun lifecycle behavior. URL helpers only.
- Test governance (TEST-GOV-001): lane, fixture cost, heavy-family browser coverage, and reviewer handoff are explicit in this plan and tasks.
- Proportionality (PROP-001): new resolver/helper is justified by current-release cross-page drift and at least two concrete surfaces.
- No premature abstraction (ABSTR-001): the resolver is bounded to one query key and does not become a generalized context framework.
- Persisted truth (PERSIST-001): no persisted truth is added.
- Behavioral state (STATE-001): no new state/status family is added.
- UI semantics (UI-SEM-001): visible chip is direct domain-to-UI mapping, not a new taxonomy.
- Shared pattern first (XCUT-001): extend Spec 314 hub registry and existing visible filter summaries before local patterns.
- Provider boundary (PROV-001): platform CTA filter uses Managed Environment database ID, not provider external ID.
- V1 explicitness / few layers: direct hard cutover, small helper, page-local data query application where appropriate.
- Spec discipline / bloat check: explicit non-goals, follow-up specs 316/317/318, and no compatibility seam.
- Filament-native UI (UI-FIL-001): use native Filament/Livewire and shared partials; no ad-hoc styling system.
- UI/UX scope, truth, and naming: scope signals must distinguish Workspace shell from explicit Environment page filter.
- UI naming: user-facing wording uses
Environment, notTenant, for the filter chip.
Test Governance Check
- Test purpose / classification by changed surface: Unit/Feature for resolver and page contracts; Browser for integrated shell/header/chip URL flows.
- Affected validation lanes: fast-feedback, confidence for existing related regressions, browser for focused smoke.
- Why this lane mix is the narrowest sufficient proof: Unit/feature tests prove URL key, resolver, data scope, and clear-link behavior. Browser tests are required because the defect is also visible UI/shell truth.
- Narrowest proving command(s):
cd apps/platform && ./vendor/bin/sail artisan test --filter=WorkspaceHubEnvironmentFiltercd apps/platform && ./vendor/bin/sail artisan test --filter=EnvironmentCtacd apps/platform && ./vendor/bin/sail artisan test --filter=WorkspaceHub- focused browser smoke command or manual browser verification documented in close-out
- Fixture / helper / factory / seed / context cost risks: Existing Workspace, ManagedEnvironment, membership/capability, operation/review/evidence/finding/provider connection factories may need small focused setup. Avoid broad global seeders.
- Expensive defaults or shared helper growth introduced?: no; browser smoke remains explicit.
- Heavy-family additions, promotions, or visibility changes: Focused Spec 315 browser smoke only.
- Surface-class relief / special coverage rule: Native Filament page/resource tests for pages/resources; browser smoke for rendered shell/context.
- Closing validation and reviewer handoff: Reviewers must verify test commands, screenshot artifacts, no legacy alias acceptance, no compatibility adapter, and no scope drift from Spec 314.
- Budget / baseline / trend follow-up: none expected.
- Review-stop questions: Does any page still parse
tenantormanaged_environment_idas CTA filter? Does any CTA emit a legacy key? Does any filtered hub set shell Environment ownership? Does any clean sidebar link retain filter state? - Escalation path: Follow-up Spec 316 only if implementation reveals broader persisted clear-state behavior is required for correctness beyond clean URL entry.
- Active feature PR close-out entry: Guardrail and Smoke Coverage.
- Why no dedicated follow-up spec is needed: The canonical CTA filter contract is the dedicated follow-up to Spec 314. Clear internals, legacy cleanup, and durable browser guard infrastructure already have follow-up specs 316, 317, and 318.
Project Structure
Documentation (this feature)
specs/315-environment-cta-explicit-filter-contract/
|-- spec.md
|-- plan.md
|-- tasks.md
|-- checklists/
| `-- requirements.md
`-- artifacts/
`-- screenshots/ # created during implementation/browser verification if useful
No research.md, data-model.md, quickstart.md, or contracts/ artifact is required for preparation because this feature introduces no data model, external API contract, or new workflow API. Runtime implementation may add notes only if a classification/exclusion needs durable documentation inside this spec folder.
Source Code (repository root)
Likely runtime files to inspect or update during implementation:
apps/platform/app/Support/Navigation/WorkspaceHubRegistry.php
apps/platform/app/Support/Navigation/WorkspaceHubEnvironmentFilter.php
apps/platform/app/Support/Navigation/WorkspaceSidebarNavigation.php
apps/platform/app/Support/ManagedEnvironmentLinks.php
apps/platform/app/Support/Operations/OperationRunLinks.php
apps/platform/app/Support/Filament/CanonicalAdminTenantFilterState.php
apps/platform/app/Filament/Pages/Monitoring/Operations.php
apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php
apps/platform/app/Filament/Pages/Governance/GovernanceInbox.php
apps/platform/app/Filament/Pages/Governance/DecisionRegister.php
apps/platform/app/Filament/Pages/Governance/FindingExceptionsQueue.php
apps/platform/app/Filament/Pages/Reviews/ReviewRegister.php
apps/platform/app/Filament/Pages/Reviews/CustomerReviewWorkspace.php
apps/platform/app/Filament/Resources/ProviderConnectionResource.php
apps/platform/resources/views/filament/
apps/platform/tests/Feature/
apps/platform/tests/Browser/
Potential classification-only inspection areas:
apps/platform/app/Filament/Pages/Monitoring/AuditLog.php
apps/platform/app/Filament/Resources/Alert*
apps/platform/app/Filament/Pages or Resources for Reports / Stored Reports
apps/platform/app/Filament/Pages or Resources for Support Requests
Structure Decision: Laravel/Filament platform app under apps/platform; new source files, if any, stay in existing app/Support and resources/views/filament locations. Tests stay in existing Pest feature/browser test directories.
Complexity Tracking
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| New resolver/helper abstraction | Environment CTA filtering spans many workspace hubs and currently has divergent key/identifier semantics | Page-local parsing or dual-param adapters would preserve drift and violate the hard-cutover policy |
| Shared visible chip partial or primitive | Filter state must be visibly consistent across hubs | Duplicated per-page chip markup would increase scope wording drift |
Phase 0: Discovery Completed During Preparation
Relevant repository facts discovered before authoring this plan:
- Spec 313 is an audit-only baseline and explicitly recommends Spec 315 for Environment CTA explicit filter standardization.
- Spec 314 has completed tasks and latest local history indicates the workspace hub navigation context contract was merged before this branch.
WorkspaceHubRegistrycurrently centralizes hub paths and clean URL behavior from Spec 314.ManagedEnvironmentLinksandOperationRunLinkscurrently have Environment-owned link helpers that use legacymanaged_environment_idsemantics in some cases.- Operations currently has
managed_environment_id,tenant_scope, and table filter state related to Environment prefiltering. - Governance Inbox and Decision Register currently parse
managed_environment_idandtenantand render a visible ManagedEnvironment filter summary. - Finding Exceptions Queue currently uses
tenantquery state andmanaged_environment_idtable filter naming. - Evidence Overview and Customer Review Workspace currently have Environment-like query/filter behavior that must hard-cut to
environment_id. - Provider Connections currently uses
managed_environment_idquery behavior around provider/external identifiers; implementation must avoid provider external tenant ID as canonical CTA filter key.
Technical Approach
1. Canonical resolver
Add or reuse a narrow helper such as:
apps/platform/app/Support/Navigation/WorkspaceHubEnvironmentFilter.php
Responsibilities:
- read only
environment_id - validate scalar/integer-like value safely
- resolve
ManagedEnvironmentby primary key inside the current Workspace - return null/no filter when
environment_idis absent - abort with 404 or existing safe no-access when
environment_idis present but invalid or cross-workspace - expose display name, ID, clear URL, and query application helpers
- never read legacy params, remembered Environment, provider external tenant ID, slug, or
Filament::getTenant()
2. Workspace hub registry alignment
Keep Spec 314 clean-entry behavior. Add a safe way for Environment-owned CTA URL builders and pages to generate or consume an explicit environment_id filter without weakening sidebar/global clean URL checks.
Review point:
environment_idmay remain forbidden for clean sidebar/global entry while being allowed for explicit CTA filter entry through the resolver. The implementation must keep those two modes separate.
3. CTA URL hard cutover
Update Environment-owned link helpers and CTAs:
- Environment Dashboard cards/header actions
- Environment Governance Overview cards
- Provider readiness/onboarding links
- Required permissions and permission posture links
- Recent Operations and OperationRun links
- ManagedEnvironment link helpers
- Review, evidence, support, report, provider connection helpers where they target workspace hubs
Output must use:
?environment_id={managed_environment_id}
and must not output:
tenant
tenant_id
managed_environment_id
tenant_scope
environment
tableFilters
4. Visible filter chip
Create or reuse a shared partial/primitive such as:
apps/platform/resources/views/filament/partials/workspace-hub-environment-filter-chip.blade.php
Required behavior:
- render only for a valid
environment_idfilter - show
Environment filter: {environment display name}or equivalent - include
Clear filter - point clear link to the clean workspace hub URL
- use calm enterprise styling consistent with existing Filament/Blade patterns
- avoid
Tenant,current tenant, or active shell Environment wording
5. Apply per workspace hub
For each critical hub:
- read only the shared resolver
- apply filter only if valid
- render shared chip
- update header/scope text to avoid misleading
All environments - keep clean URL workspace-wide
- keep shell Workspace-scoped
- add/update tests proving canonical behavior and legacy param rejection
6. Classify conditional hubs
Audit Log, Alerts, Reports/Stored Reports, and Support Requests must be classified:
- if workspace-owned and Environment-filterable: implement the same contract
- if not Environment-filterable: do not pass Environment filters through CTAs and document exclusion in implementation close-out
Data Model
No data model changes.
No migrations, seeders, backfills, stored URL migrations, compatibility transforms, retention changes, queues, scheduler, or storage volume changes.
Security and RBAC
- Existing page authorization remains the access gate.
- Environment filter narrows visible data; it never grants access.
- Cross-workspace Environment IDs must return 404 or safe no-access.
- No shell context switch or implicit workspace switch is allowed.
- No provider external tenant ID lookup is allowed for this CTA filter.
- No secrets or Graph payload changes.
Deployment / Operations
- No new environment variables.
- No migrations.
- No queues or scheduler changes.
- No storage persistence or volume changes.
- No package changes.
- No Dokploy-specific deployment changes expected.
- If implementation registers Filament assets, deployment must include
cd apps/platform && php artisan filament:assets; no registered assets are expected for the planned shared chip.
Browser Verification Plan
Use the in-app browser or existing browser test tooling after runtime changes.
Critical hubs:
- Operations
- Governance Inbox
- Decision Register
- Finding Exceptions Queue
- Provider Connections
- Evidence
- Reviews
- Customer Reviews
Flows:
- Environment Dashboard CTA -> filtered workspace hub: URL has
environment_id, no legacy params, workspace shell, visible chip, no misleading primaryAll environments, clear target is clean. - Clean sidebar/global regression: URL has no
environment_idor legacy params, no chip, workspace-wide state. - Clear link smoke: clicking clear navigates to clean URL, chip disappears, workspace-wide state is visible.
Screenshots may be saved under:
specs/315-environment-cta-explicit-filter-contract/artifacts/screenshots/
Implementation Notes
- Use direct hard cuts.
- Do not add compatibility middleware or adapter layers.
- Do not update tests by broad rebaseline; only change tests that asserted broken legacy behavior.
- Avoid deep clear-filter work unless immediate clean URL correctness breaks; move deeper clearing to Spec 316.
- Document any intentional page exclusions in the final implementation report.