## 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
10 KiB
Implementation Plan: Tenant-Owned Query Canon and Wrong-Tenant Guards
Branch: 150-tenant-owned-query-canon-and-wrong-tenant-guards | Date: 2026-03-17 | Spec: /Users/ahmeddarrazi/Documents/projects/TenantAtlas/specs/150-tenant-owned-query-canon-and-wrong-tenant-guards/spec.md
Input: Feature specification from /specs/150-tenant-owned-query-canon-and-wrong-tenant-guards/spec.md
Note: This template is filled in by the /speckit.plan command. See .specify/scripts/ for helper scripts.
Summary
Define one canonical query and record-resolution contract for tenant-owned model families so list pages, detail links, relation managers, global search, and protected actions all enforce the same tenant boundary. The implementation will build on the existing panel tenant resolution rules from ResolvesPanelTenantContext and OperateHubShell, apply a shared tenant-owned query helper to a representative first-slice family set, and back the rollout with a reusable wrong-tenant regression matrix plus a lightweight guardrail.
Technical Context
Language/Version: PHP 8.4.15
Primary Dependencies: Laravel 12, Filament v5, Livewire v4, PostgreSQL, Pest 4
Storage: PostgreSQL
Testing: Pest feature tests, Livewire component tests, architectural guard tests
Target Platform: Laravel Sail web application on macOS/local Docker and Dokploy deployment targets
Project Type: Web application
Performance Goals: No material regression on standardized admin tables; tenant-owned list and detail paths remain DB-only at render time and fail closed without broad fallback queries
Constraints: Must preserve 404 vs 403 semantics, must not widen tenant scope in workspace-admin canonical viewers, must keep existing Filament action surfaces intact, must avoid high-noise guardrails
Scale/Scope: Representative first-slice rollout across tier-1 tenant-owned Filament resources, relation managers, search paths, and guard tests
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
- Inventory-first: clarify what is “last observed” vs snapshots/backups
- Read/write separation: any writes require preview + confirmation + audit + tests
- Graph contract path: Graph calls only via
GraphClientInterface+config/graph_contracts.php - Deterministic capabilities: capability derivation is testable (snapshot/golden tests)
- RBAC-UX: two planes (/admin vs /system) remain separated; cross-plane is 404; tenant-context routes (/admin/t/{tenant}/...) are tenant-scoped; canonical workspace-context routes under /admin remain tenant-safe; non-member tenant/workspace access is 404; member-but-missing-capability is 403; authorization checks use Gates/Policies + capability registries (no raw strings, no role-string checks)
- Workspace isolation: non-member workspace access is 404; tenant-plane routes require an established workspace context; workspace context switching is separate from Filament Tenancy
- RBAC-UX: destructive-like actions require
->requiresConfirmation()and clear warning text - RBAC-UX: global search is tenant-scoped; non-members get no hints; inaccessible results are treated as not found (404 semantics)
- Tenant isolation: all reads/writes tenant-scoped; cross-tenant views are explicit and access-checked
- Run observability: long-running/remote/queued work creates/reuses
OperationRun; start surfaces enqueue-only; Monitoring is DB-only; DB-only <2s actions may skip runs but security-relevant ones still audit-log; auth handshake exception OPS-EX-AUTH-001 allows synchronous outbound HTTP on/auth/*withoutOperationRun - Ops-UX 3-surface feedback: if
OperationRunis used, feedback is exactly toast intent-only + progress surfaces + exactly-once terminalOperationRunCompleted(initiator-only); no queued/running DB notifications - Ops-UX lifecycle:
OperationRun.status/OperationRun.outcometransitions are service-owned (only viaOperationRunService); context-only updates allowed outside - Ops-UX summary counts:
summary_countskeys come fromOperationSummaryKeys::all()and values are flat numeric-only - Ops-UX guards: CI has regression guards that fail with actionable output (file + snippet) when these patterns regress
- Ops-UX system runs: initiator-null runs emit no terminal DB notification; audit remains via Monitoring; tenant-wide alerting goes through Alerts (not OperationRun notifications)
- Automation: queued/scheduled ops use locks + idempotency; handle 429/503 with backoff+jitter
- Data minimization: Inventory stores metadata + whitelisted meta; logs contain no secrets/tokens
- Badge semantics (BADGE-001): status-like badges use
BadgeCatalog/BadgeRenderer; no ad-hoc mappings; new values include tests - UI naming (UI-NAMING-001): operator-facing labels use
Verb + Object; scope (Workspace,Tenant) is never the primary action label; source/domain is secondary unless disambiguation is required; runs/toasts/audit prose use the same domain vocabulary; implementation-first terms do not appear in primary operator UI - Filament UI Action Surface Contract: for any new/modified Filament Resource/RelationManager/Page, define Header/Row/Bulk/Empty-State actions, ensure every List/Table has a record inspection affordance (prefer
recordUrl()clickable rows; do not render a lone View row action), keep max 2 visible row actions with the rest in “More”, group bulk actions, require confirmations for destructive actions (typed confirmation for large/bulk where applicable), write audit logs for mutations, enforce RBAC via central helpers (non-member 404, member missing capability 403), and ensure CI blocks merges if the contract is violated or not explicitly exempted - Filament UI UX-001 (Layout & IA): Create/Edit uses Main/Aside (3-col grid, Main=columnSpan(2), Aside=columnSpan(1)); all fields inside Sections/Cards (no naked inputs); View uses Infolists (not disabled edit forms); status badges use BADGE-001; empty states have specific title + explanation + 1 CTA; max 1 primary + 1 secondary header action; tables provide search/sort/filters for core dimensions; shared layout builders preferred for consistency
Phase 0 Gate Result: PASS
- Inventory-first and read/write separation remain intact because the feature hardens read/query and protected-action targeting behavior without introducing new Graph or snapshot flows.
- RBAC-UX requirements are central to the feature and are explicitly preserved: non-member or wrong-tenant paths stay 404, in-scope missing-capability action paths stay 403.
- No new
OperationRunbehavior is introduced. - Existing Filament action surfaces remain in place; the feature changes reachability and congruence, not the visible action inventory.
Project Structure
Documentation (this feature)
specs/[###-feature]/
├── plan.md # This file (/speckit.plan command output)
├── research.md # Phase 0 output (/speckit.plan command)
├── data-model.md # Phase 1 output (/speckit.plan command)
├── quickstart.md # Phase 1 output (/speckit.plan command)
├── contracts/ # Phase 1 output (/speckit.plan command)
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
Source Code (repository root)
app/
├── Filament/
│ ├── Concerns/
│ ├── Pages/
│ ├── Resources/
│ └── Widgets/
├── Models/
├── Policies/
├── Services/
└── Support/
routes/
└── web.php
tests/
├── Feature/
│ ├── Filament/
│ ├── Guards/
│ ├── Rbac/
│ ├── BackupScheduling/
│ ├── ProviderConnections/
│ └── Findings/
└── Unit/
Structure Decision: Use the existing Laravel web application structure. The feature is concentrated in app/Filament, app/Policies, app/Support, route-level canonical viewers in routes/web.php, and focused Pest coverage under tests/Feature.
Phase 0 Research Output
- research.md resolves the key architectural choices for the rollout.
- Resolved unknowns:
- Canonical pattern shape: shared tenant-owned query helper layered on current panel tenant resolution
- First rollout family set: derived from
TenantOwnedTablesand mapped to representative Filament resources - Global search strategy: scoped when parity is guaranteed, otherwise explicitly disabled
- Guard strategy: extend existing architectural guards with a focused tenant-owned query guard and wrong-tenant regression matrix
- Relation-manager strategy: owner-record anchored scope with explicit action-target congruence
Phase 1 Design Output
- data-model.md defines the conceptual entities for tenant-owned model families, access paths, canonical query rules, wrong-tenant guard scenarios, and explicit scope exceptions.
- contracts/tenant-owned-query-canon.openapi.yaml captures the internal HTTP semantics for list, detail, action, bulk action, canonical-viewer, and search paths.
- quickstart.md captures the recommended implementation order and verification flow.
Post-Design Constitution Check
Result: PASS
- The design stays inside the current Laravel 12 + Filament v5 + Livewire v4 stack and preserves the existing
/adminversus/systemplane separation. - No new Graph path, queue path, or snapshot path is introduced.
- The design preserves deny-as-not-found for scope failures and forbidden for in-scope capability failures.
- Global search is explicitly bounded by safe parity rules, which aligns with RBAC-UX and Filament global-search rules.
- Relation-manager and action-surface behavior remain compatible with the Filament action surface contract because the plan changes target resolution, not the visible action taxonomy.
Complexity Tracking
Fill ONLY if Constitution Check has violations that must be justified
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| None | Not applicable | Not applicable |