# Data Model: Workspace Home & Admin Landing **Feature**: 129-workspace-admin-home | **Date**: 2026-03-09 ## Overview This feature introduces no new database tables. It adds a new workspace-level read model and UI composition layer over existing workspace-owned and tenant-owned records. The design relies on existing persisted entities plus a few computed overview concepts: 1. a canonical workspace overview page, 2. capability-safe summary metrics, 3. bounded recent and needs-attention entries, 4. quick actions to existing canonical destinations. ## Existing Persistent Entities ### Workspace | Attribute | Type | Notes | |-----------|------|-------| | `id` | int | Primary workspace identity | | `name` | string | Primary visible workspace label on the overview | | `slug` | string nullable | Used in some workspace routes | | `archived_at` | timestamp nullable | Archived workspaces must not be selectable | **Relationships**: - has many `WorkspaceMembership` - has many `Tenant` **Validation / usage rules**: - A workspace overview may render only when the workspace is currently selected and the actor remains a valid member. - Archived or stale workspace context must resolve back through the chooser flow. ### WorkspaceMembership | Attribute | Type | Notes | |-----------|------|-------| | `workspace_id` | int | Workspace isolation boundary | | `user_id` | int | Actor membership | | `role` | string | Drives capability resolution | **Usage rules**: - Non-members must receive 404 deny-as-not-found for workspace home access. - Role and capability checks determine which overview surfaces and quick actions may appear. ### Tenant | Attribute | Type | Notes | |-----------|------|-------| | `id` | int | Internal identity | | `external_id` | string | Route key for tenant destinations | | `workspace_id` | int | Tenant belongs to exactly one workspace | | `name` | string | Used on chooser and downstream destinations | | `status` | string | Only active, accessible tenants count toward overview metrics | **Usage rules**: - The workspace overview never requires a selected tenant. - Tenant counts and tenant-linked actions must reflect only the current user's accessible tenant subset within the active workspace. ### OperationRun | Attribute | Type | Notes | |-----------|------|-------| | `workspace_id` | int | Workspace-bound operational scope | | `tenant_id` | int nullable | Nullable for some workspace-context monitoring cases | | `status` | string | Used for active/recent operations summaries | | `outcome` | string nullable | Used for needs-attention or recent-failure signals | | `created_at` / `updated_at` | timestamps | Used for recency ordering | **Usage rules**: - The overview may show only a bounded, capability-safe subset of operation runs. - The workspace home must not create, mutate, or transition `OperationRun` records. ### Alert / Finding Surfaces | Concept | Notes | |--------|-------| | Alerts | May contribute an alert summary count or urgent list items if a safe workspace-scoped aggregate exists | | Findings | May contribute one needs-attention metric or list entries if already available safely and cheaply | **Usage rules**: - The overview must not leak existence of unauthorized alerts or findings through counts or list rows. - These surfaces are optional at render time: if safe aggregation is unavailable, they should be hidden or degrade to empty state. ## New Computed Read Models ### WorkspaceOverviewSurface | Field | Type | Description | |------|------|-------------| | `workspace` | Workspace | Active workspace context shown on the page | | `tenant_context_active` | bool | Informational only; must not change page scope | | `summary_metrics` | list | Small KPI set shown at top of page | | `attention_items` | list | Bounded urgent items | | `recent_operations` | list | Bounded latest relevant operations | | `quick_actions` | list | Existing canonical destinations permitted for the actor | **Rules**: - Must render without a selected tenant. - Must not imply tenant context when `tenant_context_active` is false. - Must remain complete even when most sub-surfaces are empty. ### WorkspaceSummaryMetric | Field | Type | Description | |------|------|-------------| | `key` | string enum | `accessible_tenants`, `active_operations`, `alerts`, `needs_attention` | | `label` | string | UI label | | `value` | int | Capability-safe aggregate value | | `destination_url` | string nullable | Canonical existing route when drill-down is allowed | | `visible` | bool | Hidden when aggregate is not safe or not authorized | **Rules**: - Value must be cheap to compute and workspace-bounded. - No metric may reveal unauthorized tenant-owned data. ### WorkspaceAttentionItem | Field | Type | Description | |------|------|-------------| | `kind` | string enum | `failed_operation`, `high_alert`, `high_finding`, or equivalent supported type | | `title` | string | Human-readable urgent label | | `subtitle` | string nullable | Cheap contextual metadata | | `severity` | string nullable | Rendered through existing badge semantics where applicable | | `destination_url` | string | Canonical route | | `occurred_at` | datetime | Ordering field | **Rules**: - Results must be bounded and ordered by recency or severity. - Empty state is valid and must render intentionally. ### WorkspaceRecentOperationItem | Field | Type | Description | |------|------|-------------| | `operation_run_id` | int | Canonical run identity | | `title` | string | Operation label | | `status` | string | Existing operation status | | `outcome` | string nullable | Existing outcome label | | `tenant_label` | string nullable | Optional, only when safe to show | | `destination_url` | string | Canonical run detail or operations destination | | `created_at` | datetime | Recency ordering | **Rules**: - Must not spoof tenant context if no tenant is selected. - Result set must be capped. ### WorkspaceQuickAction | Field | Type | Description | |------|------|-------------| | `key` | string enum | `choose_tenant`, `operations`, `alerts`, `switch_workspace`, `manage_workspaces` | | `label` | string | Visible CTA text | | `url` | string | Canonical existing destination | | `visible` | bool | Capability-aware rendering flag | | `kind` | string enum | `context`, `navigation`, `administration` | **Rules**: - `switch_workspace` and `manage_workspaces` must remain separate entries. - No quick action may introduce a new workflow in this feature. ## State and Transition Notes ### Workspace access state ```text No selected workspace -> redirect to choose-workspace Selected workspace + valid membership -> render workspace overview at /admin Selected workspace + stale membership -> if the actor still has at least one valid workspace membership, clear stale session and redirect to choose-workspace -> if the actor is no longer entitled to the active workspace scope, return 404 deny-as-not-found ``` ### Tenant context behavior ```text Selected workspace + no selected tenant -> render workspace overview normally Selected workspace + selected tenant -> still render workspace overview at /admin -> tenant-specific work remains explicit through choose-tenant or /admin/t/{tenant} ``` ## Validation Rules | Rule | Result | |------|--------| | `/admin` means workspace home, not tenant branching | Required | | Workspace home must render without tenant context | Required | | Non-members receive 404 semantics | Required | | In-scope unauthorized actions or destinations remain hidden or 403 at target | Required | | Overview metrics and lists are capability-safe and bounded | Required | | Empty states remain intentional and complete | Required | ## Schema Impact No schema migration is expected for this feature.