169 lines
6.7 KiB
Markdown
169 lines
6.7 KiB
Markdown
# 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. |