## 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
105 lines
7.1 KiB
Markdown
105 lines
7.1 KiB
Markdown
# Data Model: Tenant-Owned Query Canon and Wrong-Tenant Guards
|
|
|
|
## 1. TenantOwnedModelFamily
|
|
|
|
- Purpose: Defines which data families are mandatory consumers of the canonical tenant-owned query rule.
|
|
- Fields:
|
|
- `name`: stable family name such as `Policy`, `PolicyVersion`, `BackupSet`, `BackupSchedule`, `RestoreRun`, `Finding`, `InventoryItem`, `EntraGroup`
|
|
- `table`: underlying tenant-owned table name
|
|
- `owns_tenant_scope`: always `true` for in-scope families
|
|
- `primary_surface`: Filament resource or page that exposes the family
|
|
- `search_posture`: `scoped`, `disabled`, or `not_applicable`
|
|
- `action_surface_status`: `declared` or `baseline_exemption`
|
|
- `action_surface_reason`: why the Action Surface Contract is declared directly or why an exemption remains temporary
|
|
- `notes`: documented exceptions or rollout deferments
|
|
- Relationships:
|
|
- One `TenantOwnedModelFamily` has many `TenantOwnedAccessPath` entries
|
|
- One `TenantOwnedModelFamily` has many `WrongTenantGuardScenario` entries
|
|
|
|
## 2. TenantOwnedAccessPath
|
|
|
|
- Purpose: Represents every way an operator can reach or act on a tenant-owned record.
|
|
- Fields:
|
|
- `family_name`: owning `TenantOwnedModelFamily`
|
|
- `path_type`: one of `index`, `detail`, `row_action`, `bulk_action`, `relation_manager`, `global_search`, `canonical_viewer`
|
|
- `scope_anchor`: `route_tenant`, `owner_record`, or `explicit_record_lookup`
|
|
- `entitlement_rule`: `workspace_and_tenant_required`
|
|
- `capability_rule`: `capability_after_scope`
|
|
- `not_entitled_result`: always `404`
|
|
- `missing_capability_result`: always `403` for protected actions after scope is established
|
|
- `record_lookup_behavior`: `same_as_list_scope` or `explicit_exception`
|
|
- State transitions:
|
|
- `accessible` when workspace membership, tenant entitlement, and lookup congruence are all satisfied
|
|
- `not_found` when workspace or tenant scope fails or when the record belongs to a foreign tenant
|
|
- `forbidden` when scope passes but the protected action capability fails
|
|
|
|
## 3. CanonicalTenantOwnedQueryRule
|
|
|
|
- Purpose: The conceptual contract that all in-scope surfaces must use to obtain records.
|
|
- Fields:
|
|
- `family_name`: target model family
|
|
- `tenant_source`: `route_tenant`, `panel_tenant`, or `explicit_record_owner`
|
|
- `workspace_source`: current workspace context
|
|
- `query_shape`: constrained family query that cannot widen beyond the owning tenant
|
|
- `action_target_check`: boolean flag requiring congruence between submitted record IDs and the resolved tenant scope
|
|
- `search_rule`: `scoped_only` or `disabled`
|
|
- Invariants:
|
|
- Must never widen from one tenant to another within the same request path
|
|
- Must fail closed when tenant context is missing for a tenant-bound surface
|
|
- Must preserve explicit record-owner entitlement checks for canonical viewers
|
|
|
|
## 4. WrongTenantGuardScenario
|
|
|
|
- Purpose: Captures the test matrix that proves the canon works across representative surfaces.
|
|
- Fields:
|
|
- `family_name`: target model family
|
|
- `scenario_type`: `positive_scope`, `wrong_tenant_index`, `wrong_tenant_detail`, `wrong_tenant_row_action`, `wrong_tenant_bulk_action`, `wrong_tenant_relation_manager`, `safe_search`
|
|
- `expected_outcome`: `200`, `403`, `404`, or `hidden_or_disabled_without_side_effect`
|
|
- `surface_class`: route test, Livewire table test, relation-manager test, or guard test
|
|
- `notes`: explanation of any Filament-specific behavior such as hidden actions not returning literal 404
|
|
|
|
## 5. ScopeException
|
|
|
|
- Purpose: Documents legitimate cases that touch tenant data but are not treated as ordinary tenant-owned surfaces.
|
|
- Fields:
|
|
- `surface_name`: human-readable surface name
|
|
- `exception_kind`: `workspace_admin_canonical_viewer`, `workspace_owned_reference_surface`, or `deferred_family`
|
|
- `why_excepted`: concise reason the canonical tenant-bound list rule does not apply directly
|
|
- `still_required_checks`: explicit list of remaining workspace, tenant, and capability checks
|
|
|
|
## 6. ResidualRolloutInventoryEntry
|
|
|
|
- Purpose: Tracks tenant-owned tables that remain outside the first-slice family map so future rollout work stays explicit instead of rediscovered ad hoc.
|
|
- Fields:
|
|
- `name`: stable residual family name
|
|
- `table`: underlying tenant-owned table name
|
|
- `likely_surface`: current relation-manager, reporting surface, or diagnostics entry point that still touches the table
|
|
- `why_not_in_first_slice`: concise reason this table is not yet a first-slice primary family
|
|
|
|
## Initial Rollout Family Map
|
|
|
|
| Family | Table | Primary Surface | Access Paths In First Slice | Search Posture | Action Surface Contract |
|
|
|---|---|---|---|---|---|
|
|
| Policy | `policies` | `PolicyResource` | index, detail, row action, bulk action, relation manager linkage | disabled until parity is guaranteed | declared |
|
|
| PolicyVersion | `policy_versions` | `PolicyVersionResource` | index, detail, row action, bulk action, relation manager | disabled until parity is guaranteed | declared |
|
|
| BackupSchedule | `backup_schedules` | `BackupScheduleResource` | index, detail, row action, bulk action | not applicable | declared |
|
|
| BackupSet | `backup_sets` | `BackupSetResource` | index, detail, row action, bulk action, relation manager | not applicable | declared |
|
|
| RestoreRun | `restore_runs` | `RestoreRunResource` | index, detail, row action, bulk action | not applicable | baseline exemption until restore-track retrofit lands |
|
|
| Finding | `findings` | `FindingResource` | index, detail, row action, bulk action | not applicable | declared |
|
|
| InventoryItem | `inventory_items` | `InventoryItemResource` | index, detail, row action, bulk action | not applicable | declared |
|
|
| EntraGroup | `entra_groups` | `EntraGroupResource` | index, detail, global search, canonical viewer | scoped | declared |
|
|
|
|
## Residual Rollout Inventory
|
|
|
|
| Family | Table | Likely Surface | Why Not In First Slice |
|
|
|---|---|---|---|
|
|
| BackupItem | `backup_items` | `BackupSetResource::BackupItemsRelationManager` | Covered through the backup-set relation manager rather than a standalone primary resource. |
|
|
| InventoryLink | `inventory_links` | `InventoryItemResource` related-links affordances | Inventory links remain subordinate navigation metadata and inherit tenant scope through inventory items. |
|
|
| EntraRoleDefinition | `entra_role_definitions` | Entra admin-role reporting and findings reference flows | Direct tenant-owned resource parity is deferred while read paths remain indirect. |
|
|
| TenantPermission | `tenant_permissions` | Permissions and onboarding diagnostics surfaces | Permission posture is enforced through dedicated diagnostics and onboarding flows, not a first-slice primary resource. |
|
|
|
|
## Explicit Exceptions In First Slice
|
|
|
|
- `ProviderConnectionResource`: tenant-owned data with a workspace-admin tenant-default surface. It is a useful reference but not the primary implementation slice for the tenant-bound canon.
|
|
- `OperationRunResource`: workspace-owned canonical monitoring surface. It remains relevant only when it deep-links into tenant-owned records.
|
|
- `AlertDeliveryResource`: mixed workspace-owned and tenant-bound semantics, therefore outside the mandatory tenant-owned family set for this slice. |