Some checks failed
Main Confidence / confidence (push) Failing after 44s
## 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
8.6 KiB
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 intakequeue at/admin/findings/intake - the fixed
UnassignedandNeeds triagequeue-view state - the post-claim handoff result that points the operator into the existing
My Findingssurface
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:
idworkspace_idtenant_idstatusseveritydue_atsubject_display_nameowner_user_idassignee_user_idreopened_attriaged_atin_progress_at
- Relationships used by this feature:
tenant()ownerUser()assigneeUser()
Relevant existing semantics:
Finding::openStatuses()defines intake inclusion and intentionally excludesacknowledged.Finding::openStatusesForQuery()remains relevant forMy 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:
idworkspace_idnameexternal_idstatus
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.assignedaction ID - the audit payload records before/after assignment state, workspace, tenant, actor, and target finding
- successful claims write an audit entry through the existing
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_idisnull
- Already-assigned findings are excluded even if overdue or reopened.
acknowledgedfindings 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 triagewhen status isneworreopenedUnassignedwhen status istriagedorin_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:
queueViewis limited tounassignedandneeds_triage.tenantFilteris clearable;fixedScopeis not.tenantFiltervalues 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 = nullat mutation time. - Claim leaves
owner_user_idunchanged. - 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
- Restrict to the current workspace.
- Restrict to visible tenant IDs.
- Restrict to
assignee_user_id IS NULL. - Restrict to
Finding::openStatuses(). - Apply the fixed queue view:
unassignedkeeps all included rowsneeds_triagekeeps onlynewandreopened
- Apply optional tenant prefilter.
- Sort overdue rows first, reopened rows second, new rows third, then remaining backlog.
- Within each bucket, rows with due dates sort by
dueAtascending, rows without due dates sort last, and remaining ties sort byfindingIddescending.
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 inNeeds triage.
Claim semantics
- Claim is not a lifecycle status transition.
- Claim performs one responsibility transition only:
assignee_user_idmoves fromnullto the current user. - Owner accountability remains unchanged.
- Successful claim makes the finding eligible for
My Findingsimmediately 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
403server-side for members missing assign capability. - Detail navigation remains tenant-scoped and must preserve existing
404and403semantics on the destination. - The derived queue state remains useful without revealing hidden tenant names, row counts, or empty-state hints.