TenantAtlas/specs/150-tenant-owned-query-canon-and-wrong-tenant-guards/data-model.md
ahmido 1f3619bd16 feat: tenant-owned query canon and wrong-tenant guards (#180)
## 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
2026-03-18 08:33:13 +00:00

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.