# 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<WorkspaceSummaryMetric> | Small KPI set shown at top of page |
| `attention_items` | list<WorkspaceAttentionItem> | Bounded urgent items |
| `recent_operations` | list<WorkspaceRecentOperationItem> | Bounded latest relevant operations |
| `quick_actions` | list<WorkspaceQuickAction> | 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.