TenantAtlas/specs/222-findings-intake-team-queue/data-model.md
ahmido 712576c447
Some checks failed
Main Confidence / confidence (push) Failing after 44s
feat: add findings intake queue and stabilize follow-up regressions (#260)
## Summary
- add the new admin findings intake queue at `/admin/findings/intake` with fixed `Unassigned` and `Needs triage` views, tenant-safe filtering, claim flow, and continuity into tenant finding detail and `My Findings`
- add Spec 222 artifacts (`spec`, `plan`, `tasks`, `research`, `data model`, `quickstart`, contract, checklist) and register the new admin page
- fix follow-up regressions uncovered during full-suite validation around findings action-surface declarations, findings list default columns, provider-blocked run messaging, operation catalog aliases, and workspace overview query volume

## Validation
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingsIntakeQueueTest.php tests/Feature/Authorization/FindingsIntakeAuthorizationTest.php tests/Feature/Findings/FindingsClaimHandoffTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact`

## Notes
- Filament remains on v5 with Livewire v4-compatible patterns
- provider registration remains unchanged in `apps/platform/bootstrap/providers.php`
- no new assets or schema changes are introduced

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #260
2026-04-21 22:54:08 +00:00

8.6 KiB

Data Model: Findings Intake & Team Queue V1

Overview

This feature does not add or modify persisted entities. It introduces three derived models:

  • the canonical admin-plane Findings intake queue at /admin/findings/intake
  • the fixed Unassigned and Needs triage queue-view state
  • the post-claim handoff result that points the operator into the existing My Findings surface

All three remain projections over existing finding, tenant membership, workspace context, and audit truth.

Existing Persistent Inputs

1. Finding

  • Purpose: Tenant-owned workflow record representing current governance or remediation 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
    • triaged_at
    • in_progress_at
  • Relationships used by this feature:
    • tenant()
    • ownerUser()
    • assigneeUser()

Relevant existing semantics:

  • Finding::openStatuses() defines intake inclusion and intentionally excludes acknowledged.
  • Finding::openStatusesForQuery() remains relevant for My Findings, but not for intake.
  • Spec 219 defines owner-versus-assignee meaning.
  • Spec 221 defines the post-claim destination when a finding becomes assigned to the current user.

2. Tenant

  • Purpose: Tenant boundary for queue disclosure, claim authorization, and tenant-plane detail drilldown.
  • Key persisted fields used by this feature:
    • id
    • workspace_id
    • name
    • external_id
    • status

3. TenantMembership And Capability Truth

  • Purpose: Per-tenant entitlement and capability boundary for queue visibility and claim.
  • Sources:
    • tenant_memberships
    • existing CapabilityResolver
  • Key values used by this feature:
    • tenant membership presence
    • role-derived TENANT_FINDINGS_VIEW
    • role-derived TENANT_FINDINGS_ASSIGN

Queue disclosure, tab badges, filter options, and claim affordances must only materialize for tenants where the actor remains a member and is authorized for the relevant finding capability.

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 intake page
    • constrains visible tenants to the current workspace
    • feeds the default active-tenant prefilter through CanonicalAdminTenantFilterState

5. AuditLog

  • Purpose: Existing audit record for security- and workflow-relevant mutations.
  • Effect on this feature:
    • successful claims write an audit entry through the existing finding.assigned action ID
    • the audit payload records before/after assignment state, workspace, tenant, actor, and target finding

Derived Presentation Entities

1. IntakeFindingRow

Logical row model for /admin/findings/intake.

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 Lifecycle badge value Finding.status
dueAt Due date if present Finding.due_at
dueState Derived urgency label such as overdue or due soon existing findings due-state logic
ownerLabel Accountable owner when present ownerUser.name
intakeReason Why the row still belongs in shared intake derived from lifecycle plus assignment truth
detailUrl Tenant finding detail route derived from tenant finding view route
navigationContext Return-path payload back to intake derived from CanonicalNavigationContext
claimEnabled Whether the current actor may claim now derived from assign capability and current claimable state

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::openStatuses()
    • assignee_user_id is null
  • Already-assigned findings are excluded even if overdue or reopened.
  • acknowledged findings are excluded.
  • Hidden-tenant or capability-blocked findings produce no row, no count, no tab badge contribution, and no tenant filter option.

Derived queue reason:

  • Needs triage when status is new or reopened
  • Unassigned when status is triaged or in_progress

2. FindingsIntakeState

Logical state model for the intake page.

Field Meaning
workspaceId Current admin workspace scope
queueView Fixed queue mode: unassigned or needs_triage
tenantFilter Optional active-tenant prefilter, defaulted from canonical admin tenant context
fixedScope Constant indicator that the page remains restricted to unassigned intake rows

Rules:

  • queueView is limited to unassigned and needs_triage.
  • tenantFilter is clearable; fixedScope is not.
  • tenantFilter values may only reference entitled tenants.
  • Invalid or stale tenant filter state is discarded rather than widening visibility.
  • Summary counts and tab badges reflect only visible intake rows after the active queue view and tenant prefilter are applied.

3. ClaimOutcome

Logical mutation result for Claim finding.

Field Meaning Source
findingId Claimed finding identifier Finding.id
tenantId Tenant scope of the claimed finding Finding.tenant_id
assigneeUserId New assignee after success current user ID
auditActionId Stable audit action identifier existing finding.assigned
nextPrimaryAction Primary handoff after success Open my findings
nextInspectAction Optional inspect fallback existing tenant finding detail route

Validation rules:

  • Actor must remain a tenant member for the target finding.
  • Actor must have TENANT_FINDINGS_ASSIGN.
  • The locked record must still have assignee_user_id = null at mutation time.
  • Claim leaves owner_user_id unchanged.
  • Claim leaves workflow status unchanged.
  • Success removes the row from intake immediately because the assignee is no longer null.
  • Conflict does not mutate the row and must return honest feedback so the queue can refresh.

State And Ordering Rules

Intake inclusion order

  1. Restrict to the current workspace.
  2. Restrict to visible tenant IDs.
  3. Restrict to assignee_user_id IS NULL.
  4. Restrict to Finding::openStatuses().
  5. Apply the fixed queue view:
    • unassigned keeps all included rows
    • needs_triage keeps only new and reopened
  6. Apply optional tenant prefilter.
  7. Sort overdue rows first, reopened rows second, new rows third, then remaining backlog.
  8. Within each 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 rows are the highest-priority bucket.
  • Reopened non-overdue rows are the next bucket.
  • New rows follow reopened rows.
  • Triaged and in-progress unassigned rows remain visible in Unassigned, but never in Needs triage.

Claim semantics

  • Claim is not a lifecycle status transition.
  • Claim performs one responsibility transition only: assignee_user_id moves from null to the current user.
  • Owner accountability remains unchanged.
  • Successful claim makes the finding eligible for My Findings immediately because the record is now assigned.
  • Stale-row conflicts must fail before save when the locked record already has an assignee.

Empty-state semantics

  • If no visible intake rows exist anywhere in scope, the page shows a calm empty state with one CTA into My Findings.
  • If the active tenant prefilter causes the empty state while other visible tenants still contain intake rows, the empty state must explain the tenant boundary and offer Clear tenant filter.
  • Neither branch may reveal hidden tenant names or hidden queue quantities.

Authorization-Sensitive Output

  • Tenant labels, tab badges, filter values, rows, and counts are only derived from entitled tenants.
  • Queue visibility remains workspace-context dependent.
  • Claim affordances remain visible only inside in-scope membership context and must still enforce 403 server-side for members missing assign capability.
  • Detail navigation remains tenant-scoped and must preserve existing 404 and 403 semantics on the destination.
  • The derived queue state remains useful without revealing hidden tenant names, row counts, or empty-state hints.