# Data Model: Findings Operator Inbox V1 ## Overview This feature does not add or modify persisted entities. It introduces two derived read models: - the canonical admin-plane `My Findings` inbox at `/admin/findings/my-work` - one compact `Assigned to me` signal on `/admin` Both remain projections over existing finding, tenant membership, and workspace context truth. ## Existing Persistent Inputs ### 1. Finding - Purpose: Tenant-owned workflow record representing current governance or execution work. - Key persisted fields used by this feature: - `id` - `workspace_id` - `tenant_id` - `status` - `severity` - `due_at` - `subject_display_name` - `owner_user_id` - `assignee_user_id` - `reopened_at` - Relationships used by this feature: - `tenant()` - `ownerUser()` - `assigneeUser()` Relevant existing semantics: - `Finding::openStatusesForQuery()` defines inbox inclusion for open work. - `Finding::openStatuses()` and terminal statuses remain unchanged. - Spec 219 defines assignee inclusion and owner-only exclusion. ### 2. Tenant - Purpose: Tenant boundary for findings ownership and tenant-plane detail navigation. - Key persisted fields used by this feature: - `id` - `workspace_id` - `name` - `external_id` - `status` ### 3. TenantMembership - Purpose: Per-tenant entitlement boundary for visibility. - Key persisted fields used by this feature: - `tenant_id` - `user_id` - `role` The inbox and overview signal must only materialize findings from tenants where the current user still has membership and findings-view entitlement. ### 4. Workspace Context - Purpose: Active workspace selection in the admin plane. - Source: Existing workspace session context, not a new persisted model for this feature. - Effect on this feature: - gates entry into the admin inbox - constrains visible tenants to the current workspace - feeds the workspace overview signal ## Derived Presentation Entities ### 1. AssignedFindingInboxRow Logical row model for `/admin/findings/my-work`. | Field | Meaning | Source | |---|---|---| | `findingId` | Target finding identifier | `Finding.id` | | `tenantId` | Tenant route scope for detail drilldown | `Finding.tenant_id` | | `tenantLabel` | Tenant name visible in the queue | `Tenant.name` | | `summary` | Operator-facing finding summary | `Finding.subject_display_name` plus existing fallback logic | | `severity` | Severity badge value | `Finding.severity` | | `status` | Workflow/lifecycle badge value | `Finding.status` | | `dueAt` | Due date if present | `Finding.due_at` | | `dueState` | Derived urgency label such as overdue or due soon | existing finding due-state logic | | `reopened` | Whether reopened context should be emphasized | `Finding.status === reopened` or existing lifecycle cues | | `ownerLabel` | Accountable owner when different from assignee | `ownerUser.name` | | `detailUrl` | Tenant finding detail route | derived from tenant finding view route | | `navigationContext` | Return-path payload back to the inbox | derived from `CanonicalNavigationContext` | Validation rules: - Row inclusion requires all of the following: - finding belongs to the current workspace - finding belongs to a tenant the current user may inspect - finding status is in `Finding::openStatusesForQuery()` - `assignee_user_id` equals the current user ID - Owner-only findings are excluded. - Hidden-tenant findings produce no row, no count, and no filter option. ### 2. MyFindingsInboxState Logical state model for the inbox page. | Field | Meaning | |---|---| | `workspaceId` | Current admin workspace scope | | `assigneeUserId` | Fixed personal-work scope | | `tenantFilter` | Optional active-tenant prefilter, defaulted from canonical admin tenant context | | `overdueOnly` | Optional urgency narrowing | | `reopenedOnly` | Optional reopened-work narrowing | | `highSeverityOnly` | Optional severity narrowing | Rules: - `assigneeUserId` is fixed and cannot be cleared in v1. - `tenantFilter` is clearable. - `tenantFilter` values may only reference entitled tenants. - Invalid or stale tenant filter state is discarded rather than widening visibility. - Inbox summary counts reflect the currently visible queue after the fixed assignee scope and any active tenant, overdue, reopened, or high-severity filters are applied. ### 3. MyFindingsSignal Logical summary model for the workspace overview. | Field | Meaning | Source | |---|---|---| | `openAssignedCount` | Count of visible open assigned findings | derived `Finding` count | | `overdueAssignedCount` | Count of visible overdue assigned findings | derived `Finding` count with `due_at < now()` | | `isCalm` | Whether the signal should render calm wording | derived from the two counts | | `headline` | Operator-facing signal summary | derived presentation copy | | `description` | Supporting copy clarifying visible-scope truth | derived presentation copy | | `ctaLabel` | Explicit drill-in label | fixed vocabulary `Open my findings` | | `ctaUrl` | Canonical inbox route | derived from the new admin page URL | Validation rules: - Counts are workspace-scoped and tenant-entitlement scoped. - The signal never implies hidden or inaccessible work. - Calm wording is only allowed when both visible counts are zero. ## State And Ordering Rules ### Inbox inclusion order 1. Restrict to the current workspace. 2. Restrict to visible tenant IDs. 3. Restrict to `assignee_user_id = current user`. 4. Restrict to `Finding::openStatusesForQuery()`. 5. Apply optional tenant/overdue/reopened/high-severity filters. 6. Sort overdue work first, reopened non-overdue work next, then remaining work. 7. Within each urgency bucket, rows with due dates sort by `dueAt` ascending, rows without due dates sort last, and remaining ties sort by `findingId` descending. ### Urgency semantics - Overdue work is the highest-priority urgency bucket. - Reopened non-overdue work is the next urgency bucket. - High-severity remains a filter and emphasis cue rather than a separate mandatory sort bucket. - Terminal findings are never part of the inbox or signal. ### Empty-state semantics - If no visible assigned open findings exist anywhere in scope, the inbox shows a calm empty state. - If the active tenant prefilter causes the empty state while other visible tenants still have work, the empty state must explain the tenant boundary and provide a clear fallback CTA. ## Authorization-sensitive Output - Tenant labels, filter values, rows, and counts are only derived from entitled tenants. - The inbox itself is workspace-context dependent. - Detail navigation remains tenant-scoped and must preserve existing `404`/`403` semantics on the destination. - The derived signal and row model remain useful without ever revealing hidden tenant names or quantities.