## Summary - introduce a shared tenant-owned query and record-resolution canon for first-slice Filament resources - harden direct views, row actions, bulk actions, relation managers, and workspace-admin canonical viewers against wrong-tenant access - add registry-backed rollout metadata, search posture handling, architectural guards, and focused Pest coverage for scope parity and 404/403 semantics ## Included - Spec 150 package under `specs/150-tenant-owned-query-canon-and-wrong-tenant-guards/` - shared support classes: `TenantOwnedModelFamilies`, `TenantOwnedQueryScope`, `TenantOwnedRecordResolver` - shared Filament concern: `InteractsWithTenantOwnedRecords` - resource/page/policy hardening across findings, policies, policy versions, backup schedules, backup sets, restore runs, inventory items, and Entra groups - additional regression coverage for canonical tenant state, wrong-tenant record resolution, relation-manager congruence, and action-surface guardrails ## Validation - `vendor/bin/sail artisan test --compact` passed - full suite result: `2733 passed, 8 skipped` - formatting applied with `vendor/bin/sail bin pint --dirty --format agent` ## Notes - Livewire v4.0+ compliant via existing Filament v5 stack - provider registration remains in `bootstrap/providers.php` - globally searchable first-slice posture: Entra groups scoped; policies and policy versions explicitly disabled - destructive actions continue to use confirmation and policy authorization - no new Filament assets added; existing deployment flow remains unchanged, including `php artisan filament:assets` when registered assets are used Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #180
187 lines
19 KiB
Markdown
187 lines
19 KiB
Markdown
# Feature Specification: Tenant-Owned Query Canon and Wrong-Tenant Guards
|
|
|
|
**Feature Branch**: `150-tenant-owned-query-canon-and-wrong-tenant-guards`
|
|
**Created**: 2026-03-17
|
|
**Status**: Draft
|
|
**Input**: User description: "zieh den strategisch sinnvollsten nächsten spec auf spec candidates"
|
|
|
|
## Spec Scope Fields *(mandatory)*
|
|
|
|
- **Scope**: tenant + canonical-view
|
|
- **Primary Routes**:
|
|
- Tenant-owned resource lists, detail pages, and actions under `/admin/t/{tenant}/...`
|
|
- Workspace-admin canonical viewers and direct record URLs under `/admin/...` where tenant-owned records may still be opened safely
|
|
- Global search and deep-link entry paths for in-scope tenant-owned resources
|
|
- Relation-manager and embedded table surfaces that resolve tenant-owned records inside a tenant-bound page
|
|
- **Data Ownership**:
|
|
- In-scope records remain tenant-owned records inside the existing workspace boundary.
|
|
- Workspace-owned monitoring and audit surfaces remain workspace-owned, but any lookup or drill-down that opens a tenant-owned record must follow the same tenant-owned query rules.
|
|
- This feature introduces no new business domain and no new ownership model. It defines one canonical way to resolve, list, inspect, and act on tenant-owned records.
|
|
- **RBAC**:
|
|
- Workspace membership remains the first boundary.
|
|
- Tenant entitlement remains required before any tenant-owned record may be listed, resolved, or acted on.
|
|
- Capability checks continue to gate protected actions after workspace and tenant scope have been established.
|
|
- Non-members or users outside the relevant tenant scope remain deny-as-not-found. In-scope users lacking the required capability remain forbidden for protected actions.
|
|
|
|
For canonical-view specs, the spec MUST define:
|
|
|
|
- **Default filter behavior when tenant-context is active**: Tenant-owned resource lists and relation tables default to the route or selected tenant context already established by the page. Workspace-admin canonical viewers must never infer a broader tenant scope when tenant context is missing; they either resolve the specific entitled record safely or return deny-as-not-found.
|
|
- **Explicit entitlement checks preventing cross-tenant leakage**: Every in-scope list query, record lookup, row action, bulk action, relation-manager query, and global-search result must validate workspace membership and tenant entitlement against the record's actual owner tenant before any data, labels, or action affordances are shown.
|
|
|
|
## User Scenarios & Testing *(mandatory)*
|
|
|
|
### User Story 1 - Trust tenant-owned lists and detail links (Priority: P1)
|
|
|
|
As an operator, I want tenant-owned resources to show only records from the tenant I am currently entitled to, so that lists, detail pages, and bookmarks never drift into another tenant's data.
|
|
|
|
**Why this priority**: This is the core isolation promise for tenant-owned data. If record lookup can drift across tenants, every downstream governance surface becomes unreliable.
|
|
|
|
**Independent Test**: Can be fully tested by preparing equivalent records in two tenants, opening the list and a direct detail link in one tenant context, and confirming that only the in-scope tenant's record can be listed or opened.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** an operator is entitled to tenant A and tenant B contains a record with the same resource type, **When** the operator opens the tenant A list, **Then** only tenant A records appear.
|
|
2. **Given** an operator has a bookmarked detail URL for a tenant B record while operating in tenant A, **When** the operator opens the URL without tenant B entitlement, **Then** the request is denied as not found and no tenant B details are revealed.
|
|
3. **Given** an operator is entitled to the record's tenant, **When** the operator opens a direct detail URL, **Then** the detail page resolves the correct record without needing a broader fallback query.
|
|
|
|
---
|
|
|
|
### User Story 2 - Block wrong-tenant mutations and bulk actions (Priority: P1)
|
|
|
|
As an operator, I want row actions, bulk actions, and related-record actions to use the same tenant boundary as the list I am looking at, so that I cannot accidentally trigger a mutation against another tenant's record.
|
|
|
|
**Why this priority**: Read-scope isolation is not enough if a hidden wrong-tenant action path can still mutate data.
|
|
|
|
**Independent Test**: Can be fully tested by attempting row actions, bulk actions, and relation-manager actions against foreign-tenant records and confirming they never execute.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** a tenant-owned list is scoped to tenant A, **When** a foreign-tenant record identifier is submitted to a row action or bulk action path, **Then** the action is refused before any mutation occurs.
|
|
2. **Given** a tenant-bound page exposes a relation manager or embedded table, **When** the operator opens related records or triggers actions, **Then** all related queries and actions remain bound to the owning tenant.
|
|
3. **Given** an operator is entitled to the tenant but lacks the required capability, **When** the operator triggers a protected action on an in-scope record, **Then** the system returns forbidden rather than widening or hiding the record lookup.
|
|
|
|
---
|
|
|
|
### User Story 3 - Maintain one canonical query pattern (Priority: P2)
|
|
|
|
As a maintainer, I want one canonical query and record-resolution pattern for tenant-owned model families, so that future resources do not keep reintroducing ad hoc tenant filters and uneven wrong-tenant tests.
|
|
|
|
**Why this priority**: The real strategic value is architectural consistency. Local `tenant_id` filtering fixes do not scale.
|
|
|
|
**Independent Test**: Can be fully tested by applying the same wrong-tenant regression scenarios to multiple representative resource families and verifying they all follow the same allow and deny behavior.
|
|
|
|
**Acceptance Scenarios**:
|
|
|
|
1. **Given** two different tenant-owned resource families are in scope, **When** wrong-tenant index, detail, row action, and bulk action scenarios are exercised, **Then** both families follow the same canonical scope rules.
|
|
2. **Given** a new tenant-owned resource is added later, **When** it adopts the canonical query pattern, **Then** maintainers can reuse the same regression expectations instead of inventing a new local rule.
|
|
|
|
### Edge Cases
|
|
|
|
- A direct URL points to a valid record that belongs to the same workspace but a different tenant than the current route tenant.
|
|
- A row action or bulk action receives forged record identifiers that were never visible in the current table.
|
|
- A relation manager or embedded table inherits a correct page tenant but uses a broader related-record lookup path.
|
|
- Global search or linked table columns can resolve a tenant-owned record without first visiting the list page.
|
|
- Persisted table state or remembered filters reference a record from a tenant the operator no longer has access to.
|
|
- A workspace-admin canonical viewer can legitimately open a tenant-owned record only when the operator is entitled to that record's tenant; otherwise the record must disappear safely.
|
|
|
|
## Requirements *(mandatory)*
|
|
|
|
**Constitution alignment (required):** This feature introduces no new Microsoft Graph calls, no new long-running work, and no new scheduled work. It is a query and authorization hardening feature for tenant-owned reads and mutations. Because it changes server-side record resolution and protected action behavior, it must explicitly define tenant isolation, deny semantics, and regression coverage. Existing audit behavior for sensitive mutations remains in force; this feature prevents wrong-tenant execution rather than creating a new mutation type.
|
|
|
|
**Constitution alignment (OPS-UX):** This feature does not create or reuse `OperationRun` types. Monitoring and run observability are unaffected except where a workspace-admin surface opens a tenant-owned record and must apply the same tenant-owned lookup rule.
|
|
|
|
**Constitution alignment (RBAC-UX):** This feature changes authorization behavior in tenant-bound `/admin/t/{tenant}/...` flows and in workspace-admin canonical viewers under `/admin/...` that open tenant-owned records. Cross-plane broadening is forbidden. Non-members and users not entitled to the relevant tenant scope remain deny-as-not-found. Users already in the correct scope but lacking a required capability remain forbidden for protected actions. Authorization must be enforced server-side for list queries, record lookup, global search, row actions, bulk actions, and relation-manager actions. The canonical capability registry remains the only capability source. Global search must remain tenant-safe and non-member-safe. Existing destructive actions remain confirmation-gated; this feature does not introduce a new destructive action family.
|
|
|
|
**Constitution alignment (OPS-EX-AUTH-001):** Not applicable. No authentication handshake or synchronous outbound login behavior is involved.
|
|
|
|
**Constitution alignment (BADGE-001):** If in-scope resources show status or outcome badges, those meanings remain centralized. This feature changes which records are reachable, not the meaning of their badges.
|
|
|
|
**Constitution alignment (UI-NAMING-001):** Operator-facing verbs remain existing resource verbs such as `View`, `Edit`, `Delete`, `Restore`, or domain-specific actions. No new vocabulary is needed. The important naming rule is that deny behavior must never leak foreign-tenant hints through button text, search labels, or empty-state copy.
|
|
|
|
**Constitution alignment (Filament Action Surfaces):** This feature modifies existing Filament Resources, RelationManagers, and Pages by standardizing how they query and resolve tenant-owned records. The Action Surface Contract remains satisfied because visible actions stay domain-owned and this feature changes only scope correctness, action reachability, and regression expectations.
|
|
|
|
**Constitution alignment (UX-001 — Layout & Information Architecture):** In-scope Filament screens keep their current layouts. The UX obligation for this feature is that lists, detail pages, relation tables, and empty states reflect the true tenant scope consistently and never imply access to a foreign tenant record.
|
|
|
|
### Functional Requirements
|
|
|
|
- **FR-150-001**: The system MUST define one canonical query-entry rule for every in-scope tenant-owned model family.
|
|
- **FR-150-002**: In-scope tenant-owned resource lists MUST derive their records from the canonical tenant-owned query rule rather than from ad hoc page-local filtering.
|
|
- **FR-150-003**: In-scope direct record lookup and detail-page resolution MUST use the same tenant-owned query rule as the corresponding list flow.
|
|
- **FR-150-004**: In-scope row actions, bulk actions, and relation-manager actions MUST resolve their target records through the same tenant-owned query rule as the visible surface that exposed them.
|
|
- **FR-150-005**: The system MUST prevent any in-scope protected action from executing against a record owned by a foreign tenant, even when a forged record identifier is submitted.
|
|
- **FR-150-006**: Global search for in-scope tenant-owned resources MUST either obey the same tenant-owned query rule as list and detail flows or be disabled for that resource.
|
|
- **FR-150-007**: Workspace-admin canonical viewers that open tenant-owned records MUST verify entitlement to the record's owning tenant before any tenant-owned data is rendered.
|
|
- **FR-150-008**: Tenant-bound pages MUST treat the route tenant as the visible scope anchor and MUST NOT silently widen record lookup beyond that tenant.
|
|
- **FR-150-009**: When the operator lacks workspace membership or tenant entitlement for the target record, the system MUST return deny-as-not-found rather than a broader fallback or a revealing error.
|
|
- **FR-150-010**: When the operator is in the correct workspace and tenant scope but lacks the required capability for a protected action, the system MUST return forbidden semantics for that action.
|
|
- **FR-150-011**: The product MUST define which model families count as mandatory tenant-owned families for this canonical query rule in the first rollout slice.
|
|
- **FR-150-012**: The first rollout slice MUST cover at least one representative set of tier-1 tenant-owned resources across list, detail, row action, bulk action, relation-manager, and global-search paths.
|
|
- **FR-150-013**: In-scope relation managers and embedded tables MUST not use broader related-record lookups than the tenant-bound page that contains them.
|
|
- **FR-150-014**: Persisted table state, filters, and search inputs MUST NOT reintroduce access to foreign-tenant records when the current tenant scope changes.
|
|
- **FR-150-015**: The product MUST define a documented exception mechanism for legitimate edge cases where a surface is not tenant-owned even though it references tenant data.
|
|
- **FR-150-016**: New in-scope code MUST NOT introduce fresh page-local tenant filtering where the canonical tenant-owned query rule is required.
|
|
- **FR-150-017**: The feature MUST add focused regression coverage for wrong-tenant index, detail, row action, bulk action, and relation-manager scenarios.
|
|
- **FR-150-018**: The feature MUST add focused regression coverage for at least one positive in-scope path and one negative wrong-tenant path per representative tenant-owned family.
|
|
- **FR-150-019**: The feature MUST add a lightweight guardrail that helps detect new forbidden query patterns on in-scope tenant-owned surfaces without generating high-noise false positives.
|
|
- **FR-150-020**: The feature MUST leave a documented residual inventory of tenant-owned surfaces that are already compliant, explicitly exempt, or intentionally deferred.
|
|
|
|
### Non-Goals
|
|
|
|
- Redesigning the workspace and tenant panel information architecture
|
|
- Reworking workspace-owned monitoring surfaces that do not open tenant-owned records
|
|
- Introducing a new business domain, tenancy model, or permission model
|
|
- Replacing all ad hoc filters everywhere in the codebase in one pass
|
|
- Broadly rewriting already-compliant resources only for stylistic consistency
|
|
|
|
### Assumptions
|
|
|
|
- Specs 135 and 136 already established the broader canonical tenant-context direction for admin and tenant-panel flows.
|
|
- Spec 149 already covers execution-time reauthorization for queued mutation work; this feature is the complementary hardening layer for record lookup and action targeting.
|
|
- The product already has a meaningful set of tier-1 tenant-owned resources where wrong-tenant regressions are worth enforcing systematically.
|
|
- Existing destructive actions already have domain-level confirmation and audit behavior; this feature ensures they cannot reach foreign-tenant records.
|
|
|
|
### Dependencies
|
|
|
|
- Canonical tenant context direction from Specs 135 and 136
|
|
- Existing workspace membership and tenant entitlement enforcement
|
|
- Existing tenant-owned Filament resources, relation managers, and canonical viewers that can be exercised through focused regression tests
|
|
- Existing global-search configuration for tenant-owned resources
|
|
|
|
## UI Action Matrix *(mandatory when Filament is changed)*
|
|
|
|
| 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 |
|
|
|---|---|---|---|---|---|---|---|---|---|---|
|
|
| Tenant-owned resources | Tenant-bound lists and detail pages under `/admin/t/{tenant}/...` | Existing header actions unchanged | Table rows and record detail links remain the inspect affordance | Existing domain actions only | Existing grouped bulk actions only | Existing empty states remain | Existing view actions only | Existing save and cancel semantics remain | Existing audit behavior only | This feature changes which records are reachable, not the action inventory. Row and bulk actions must use the same tenant-owned record source as the list. |
|
|
| Relation managers and embedded tenant-owned tables | Tenant-bound pages and record detail screens | Existing page actions unchanged | Embedded table rows and relation links remain the inspect affordance | Existing domain actions only | Existing grouped bulk actions only | Existing empty states remain | Existing view actions only | Existing save and cancel semantics remain | Existing audit behavior only | Related-record queries must not widen beyond the owning tenant of the page. |
|
|
| Workspace-admin canonical viewers for tenant-owned records | Direct record viewers and deep-link pages under `/admin/...` | Existing non-destructive header actions unchanged | Direct record view remains the inspect affordance | Existing domain actions only when entitled | None new | Existing safe not-found behavior remains | Existing view actions only | Existing save and cancel semantics remain | Existing audit behavior only | Exemption: this surface may open a tenant-owned record outside `/admin/t/{tenant}/...`, but only through explicit entitled record lookup, never through broader list-style fallback. |
|
|
| Global search entry points for tenant-owned resources | Global search results and linked resource destinations | None | Search result title and link | None new | None | Not applicable | Not applicable | Not applicable | No new audit event | Search remains allowed only where the result can follow the same tenant-owned lookup rule safely. |
|
|
|
|
### Key Entities *(include if feature involves data)*
|
|
|
|
- **Tenant-Owned Record**: Any record whose visibility and mutability are bound to a specific tenant inside a workspace.
|
|
- **Canonical Tenant-Owned Query Rule**: The single allowed way to list, resolve, and act on a tenant-owned record family.
|
|
- **Wrong-Tenant Access Path**: Any index, detail, search, row action, bulk action, or related-record path that attempts to reach a record outside the current entitled tenant scope.
|
|
- **Sensitive Tenant Action Path**: A protected mutation or high-impact action that must prove the target record belongs to the current entitled tenant scope before it executes.
|
|
|
|
## Success Criteria *(mandatory)*
|
|
|
|
### Measurable Outcomes
|
|
|
|
- **SC-150-001**: In focused rollout coverage, 100% of covered tenant-owned resource lists return only records from the entitled tenant scope.
|
|
- **SC-150-002**: In focused wrong-tenant regression coverage, 100% of covered direct detail and deep-link attempts to foreign-tenant records resolve as deny-as-not-found.
|
|
- **SC-150-003**: In focused wrong-tenant mutation coverage, 0 covered row actions, bulk actions, or relation-manager actions execute against a foreign-tenant record.
|
|
- **SC-150-004**: In focused authorization coverage, 100% of covered in-scope capability denials return forbidden semantics without widening or hiding the underlying in-scope record lookup.
|
|
- **SC-150-005**: In the first rollout slice, every covered tenant-owned family passes the same positive-path and wrong-tenant-path regression matrix for index, detail, and action behavior.
|
|
- **SC-150-006**: The agreed guardrail flags new forbidden tenant-owned query patterns on covered surfaces before release without requiring a full codebase security audit.
|
|
|
|
## Risks
|
|
|
|
- Fixing only list queries while leaving direct record lookup or action targeting broader would create false confidence.
|
|
- An overly broad guardrail could become noisy and get ignored.
|
|
- Treating workspace-admin canonical viewers as ordinary tenant-bound lists would break legitimate deep-link use cases.
|
|
- Failing to define documented exemptions would push maintainers back toward ad hoc exceptions in code.
|
|
|
|
## Final Direction
|
|
|
|
Tenant-owned data must have one scope rule, not five slightly different ones. This feature defines a canonical query and lookup contract for tenant-owned records, then backs it with wrong-tenant regression coverage so list pages, deep links, relation managers, and protected actions all enforce the same tenant boundary.
|