# Feature Specification: Findings Intake & Team Queue V1 **Feature Branch**: `222-findings-intake-team-queue` **Created**: 2026-04-21 **Status**: Draft **Input**: User description: "Findings Intake & Team Queue v1" ## Spec Candidate Check *(mandatory - SPEC-GATE-001)* - **Problem**: Spec 221 created a trustworthy personal queue for assigned findings, but it did not solve where newly detected or otherwise unassigned findings enter the workflow before someone claims them. - **Today's failure**: Unassigned backlog is still scattered across tenant-local findings pages and ad hoc filters. Operators cannot answer "what is still unowned right now?" from one shared workspace-safe surface, so intake work is easy to miss, duplicate, or defer accidentally. - **User-visible improvement**: One shared intake queue shows visible unassigned open findings across tenants, separates triage-first backlog from later execution, and provides a direct handoff into personal work when an operator claims an item. - **Smallest enterprise-capable version**: A canonical read-first intake page for unassigned open findings, fixed `Unassigned` and `Needs triage` views, one optional self-claim action, and tenant-safe drilldown into the existing finding detail and `My Findings` execution surface. - **Explicit non-goals**: No team model, no capacity dashboard, no auto-routing, no escalation engine, no load balancing, no notifications, no bulk claim workflow, and no new permission system. - **Permanent complexity imported**: One canonical intake page, one derived intake-query contract, one quick claim action reusing existing assignment semantics, one fixed queue vocabulary for `Unassigned` and `Needs triage`, and focused regression coverage for visibility, claim safety, and handoff continuity. - **Why now**: Spec 219 clarified owner-versus-assignee meaning and Spec 221 made assigned work discoverable. The next smallest missing workflow slice is the shared pre-assignment intake surface that feeds those personal queues. - **Why not local**: A tenant-local filter still forces tenant hopping and does not create one trustworthy workspace-wide intake queue or a consistent handoff from shared backlog into personal execution. - **Approval class**: Core Enterprise - **Red flags triggered**: None after scope cut. The spec adds one derived queue and one reuse-first claim action rather than a broader team-routing framework. - **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 2 | Produktnaehe: 2 | Wiederverwendung: 1 | **Gesamt: 11/12** - **Decision**: approve ## Spec Scope Fields *(mandatory)* - **Scope**: canonical-view - **Primary Routes**: - `/admin/findings/intake` as the new canonical shared intake queue - `/admin/findings/my-work` as the existing personal execution destination after claim - `/admin/t/{tenant}/findings` as the existing tenant findings list fallback - `/admin/t/{tenant}/findings/{finding}` as the existing tenant finding detail drilldown - **Data Ownership**: Tenant-owned findings remain the only source of truth. The intake queue is a derived cross-tenant view over existing finding status, assignee, owner, due state, and tenant-entitlement truth. - **RBAC**: Workspace membership is required for the canonical intake page in the admin plane. Every visible row and count additionally requires tenant entitlement plus the existing findings view capability for the referenced tenant. The `Claim` action reuses the existing findings assign capability. Non-members and out-of-scope users remain deny-as-not-found. Members missing the required capability remain forbidden on protected actions. For canonical-view specs, the spec MUST define: - **Default filter behavior when tenant-context is active**: The intake page always keeps its fixed unassigned-open scope. When an active tenant context exists, the page additionally applies that tenant as a default prefilter while allowing the operator to clear only the tenant prefilter, not the intake scope itself. - **Explicit entitlement checks preventing cross-tenant leakage**: Rows, counts, tenant filter values, claim affordances, and drilldown links are derived only from tenants the current user may already inspect. Hidden tenants contribute nothing to queue rows, counts, filter values, or empty-state hints. ## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)* | Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note | |---|---|---|---|---|---|---| | Findings intake queue | yes | Native Filament page + existing table, filter, badge, and row-action primitives | Same findings workflow family as tenant findings list, finding detail, and `My Findings` | table, fixed queue views, claim action, return path | no | Read-first shared queue only; no new mutation family beyond claim | ## Decision-First Surface Role *(mandatory when operator-facing surfaces are changed)* | Surface | Decision Role | Human-in-the-loop Moment | Immediately Visible for First Decision | On-Demand Detail / Evidence | Why This Is Primary or Why Not | Workflow Alignment | Attention-load Reduction | |---|---|---|---|---|---|---|---| | Findings intake queue | Primary Decision Surface | The operator reviews unowned findings and decides what needs first triage or self-claim before personal execution begins | Tenant, finding summary, severity, lifecycle status, due or overdue state, owner when present, and why the row is still in intake | Full finding detail, evidence, audit trail, exception context, and tenant-local workflow actions after opening the finding | Primary because this is the missing shared entry point before work moves into `My Findings` | Aligns the first-routing workflow around one queue instead of tenant hopping | Removes repeated searches across tenant findings pages just to locate unassigned backlog | ## UI/UX Surface Classification *(mandatory when operator-facing surfaces are changed)* | Surface | Action Surface Class | Surface Type | Likely Next Operator Action | Primary Inspect/Open Model | Row Click | Secondary Actions Placement | Destructive Actions Placement | Canonical Collection Route | Canonical Detail Route | Scope Signals | Canonical Noun | Critical Truth Visible by Default | Exception Type / Justification | |---|---|---|---|---|---|---|---|---|---|---|---|---|---| | Findings intake queue | List / Table / Bulk | Workflow queue / list-first canonical view | Claim a finding or open it for verification | Finding | required | One inline safe shortcut for `Claim`; fixed queue-view controls stay outside row-action noise | None on the queue; dangerous lifecycle actions remain on tenant-local finding detail | /admin/findings/intake | /admin/t/{tenant}/findings/{finding} | Active workspace, optional active-tenant prefilter, tenant column, fixed queue-view indicator | Findings intake / Finding | Which open findings are still unassigned, which ones still need first triage, and which tenant they belong to | none | ## Operator Surface Contract *(mandatory when operator-facing surfaces are changed)* | Surface | Primary Persona | Decision / Operator Action Supported | Surface Type | Primary Operator Question | Default-visible Information | Diagnostics-only Information | Status Dimensions Used | Mutation Scope | Primary Actions | Dangerous Actions | |---|---|---|---|---|---|---|---|---|---|---| | Findings intake queue | Tenant operator or tenant manager | Decide which unassigned finding needs first team attention and optionally claim it into personal execution | Shared workflow queue | What open findings are still unclaimed right now, and which ones should move into execution first? | Tenant, finding summary, severity, lifecycle status, due date or overdue state, owner when present, and queue reason (`Unassigned` or `Needs triage`) | Raw evidence, run context, exception history, and full audit trail | lifecycle, due urgency, severity, responsibility state | TenantPilot only | Claim finding, Open finding, Apply queue filters | none | ## Proportionality Review *(mandatory when structural complexity is introduced)* - **New source of truth?**: no - **New persisted entity/table/artifact?**: no - **New abstraction?**: no - **New enum/state/reason family?**: no - **New cross-domain UI framework/taxonomy?**: no - **Current operator problem**: Unassigned findings already exist, but there is no single trustworthy place to review and route that shared backlog before it becomes personal work. - **Existing structure is insufficient because**: Tenant-local findings pages and ad hoc filters do not answer the cross-tenant intake question and do not provide a clean handoff from shared backlog into `My Findings`. - **Narrowest correct implementation**: One derived intake page with fixed inclusion rules and one self-claim action that reuses the current assignment model and existing finding detail as the deeper workflow surface. - **Ownership cost**: One cross-tenant queue query, one small queue vocabulary, one claim race-safety rule, and focused regression tests for visibility, claim authorization, and handoff continuity. - **Alternative intentionally rejected**: A full team workboard, notifications-first intake model, or auto-routing engine was rejected because those shapes add durable workflow machinery before the product has proven the smaller intake queue. - **Release truth**: Current-release truth. This slice makes existing findings workflow usable before later escalation and hygiene follow-ups land. ### Compatibility posture This feature assumes a pre-production environment. Backward compatibility, legacy aliases, migration shims, historical fixtures, and compatibility-specific tests are out of scope unless explicitly required by this spec. Canonical replacement is preferred over preservation. ## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)* - **Test purpose / classification**: Feature - **Validation lane(s)**: fast-feedback, confidence - **Why this classification and these lanes are sufficient**: The change is proven by visible operator behavior on one canonical queue and one claim handoff into an existing surface. Focused feature coverage is sufficient to prove visibility, authorization, claim safety, and queue continuity without introducing browser or heavy-governance cost. - **New or expanded test families**: Add focused coverage for the intake queue visibility contract, active-tenant prefilter behavior, `Needs triage` slice behavior, positive and negative claim authorization, and post-claim handoff into `My Findings`. - **Fixture / helper cost impact**: Low to moderate. Tests need one workspace, multiple visible and hidden tenants, memberships, and findings in open and terminal states with explicit owner and assignee combinations. - **Heavy-family visibility / justification**: none - **Special surface test profile**: global-context-shell - **Standard-native relief or required special coverage**: Ordinary feature coverage is sufficient, plus explicit assertions that hidden-tenant findings never leak into rows or filter values, that assigned findings never re-enter intake, that `Needs triage` remains a strict subset of unassigned open work, and that claim removes the row from intake before the operator continues in `My Findings`. - **Reviewer handoff**: Reviewers must confirm that intake includes only unassigned open findings from visible tenants, that claim reuses the existing assign permission instead of inventing a new one, that stale rows cannot silently overwrite another operator's claim, and that tenant-prefilter empty states explain scope narrowing honestly. - **Budget / baseline / trend impact**: none - **Escalation needed**: none - **Active feature PR close-out entry**: Guardrail - **Planned validation commands**: - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingsIntakeQueueTest.php tests/Feature/Authorization/FindingsIntakeAuthorizationTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingsClaimHandoffTest.php` ## User Scenarios & Testing *(mandatory)* ### User Story 1 - See shared unassigned backlog in one queue (Priority: P1) As a tenant operator, I want one shared intake queue across my visible tenants so I can see unassigned open findings before they disappear into tenant-local lists. **Why this priority**: This is the core missing workflow slice. If unassigned backlog remains scattered, later claim, escalation, or hygiene work starts from unreliable visibility. **Independent Test**: Can be fully tested by seeding multiple visible and hidden tenants with assigned and unassigned findings, then verifying that the intake queue shows only visible unassigned open findings. **Acceptance Scenarios**: 1. **Given** the current user can inspect multiple tenants with unassigned open findings, **When** the user opens the intake queue, **Then** the page shows only those visible unassigned open findings with tenant and urgency context. 2. **Given** an active tenant context exists, **When** the user opens the intake queue, **Then** the queue is prefiltered to that tenant while keeping the shared unassigned-open scope intact. 3. **Given** a finding is already assigned to any user, **When** the intake queue renders, **Then** that finding does not appear even if it is still open or overdue. --- ### User Story 2 - Separate triage-first backlog from later shared backlog (Priority: P1) As a tenant manager, I want a fixed `Needs triage` view inside the intake queue so brand-new or reopened unassigned findings are not buried under older shared backlog. **Why this priority**: Visibility alone is not enough. The shared queue must also show which rows still need first routing versus which ones are simply waiting for someone to claim them. **Independent Test**: Can be fully tested by seeding unassigned findings in `new`, `reopened`, `triaged`, and `in_progress` states, then verifying the difference between the default unassigned view and the `Needs triage` subset. **Acceptance Scenarios**: 1. **Given** unassigned findings exist in `new`, `reopened`, `triaged`, and `in_progress` states, **When** the operator selects `Needs triage`, **Then** only `new` and `reopened` findings remain visible. 2. **Given** the same backlog, **When** the operator selects `Unassigned`, **Then** all unassigned open findings remain visible regardless of open workflow state. 3. **Given** a `reopened` finding is already assigned, **When** the operator selects `Needs triage`, **Then** that finding stays out of intake because the queue is strictly pre-assignment. --- ### User Story 3 - Claim a finding into personal execution (Priority: P2) As a tenant operator, I want to claim an item from the shared intake queue so it leaves shared backlog and becomes personal work without opening a separate reassignment workflow first. **Why this priority**: This is the smallest action that turns shared backlog into owned execution while keeping the intake surface calm and bounded. **Independent Test**: Can be fully tested by claiming an eligible intake item, verifying that assignment changes to the current user, that the row disappears from intake, and that the operator has a clear next step into `My Findings` or the existing finding detail. **Acceptance Scenarios**: 1. **Given** an unassigned open finding is visible in intake and the operator has assign permission, **When** the operator confirms the claim after reviewing the lightweight preview, **Then** the finding assignee becomes the current user, the row leaves intake, and the operator can continue into `My Findings` or the finding detail. 2. **Given** a workspace member can view findings but lacks assign capability, **When** the intake queue renders, **Then** the finding remains inspectable but the operator cannot successfully claim it and the server rejects direct claim attempts with `403`. 3. **Given** another operator claims the same finding after the current queue has already loaded, **When** the current operator attempts to claim the stale row, **Then** the system refuses to overwrite the existing assignee and refreshes the queue truth honestly. ### Edge Cases - A finding may already have an owner but no assignee; it still belongs in intake because active execution is unclaimed. - An active tenant prefilter may produce an empty queue while other visible tenants still contain intake items; the empty state must explain the tenant boundary instead of claiming that no intake work exists anywhere. - A `reopened` finding assigned to a user must stay in personal execution surfaces, not re-enter intake, because the queue is strictly pre-assignment. - Claim attempts can race; the mutation must re-check current assignee server-side and refuse silent overwrite. - Hidden-tenant or capability-blocked findings must not influence queue rows, counts, tenant filter values, or empty-state hints. ## Requirements *(mandatory)* **Constitution alignment (required):** This feature adds no Microsoft Graph calls and no new long-running work. It introduces one derived read surface and one DB-only claim mutation. Claim intentionally skips `OperationRun` because it is a short tenant-local assignment write, but it MUST present a lightweight pre-commit preview of the exact assignee change, require explicit confirmation before writing, and write an `AuditLog` entry that records actor, workspace, tenant, target finding, and before/after assignment state. **Constitution alignment (RBAC-UX):** The feature operates in the admin `/admin` plane for the canonical intake queue and crosses into tenant-context detail routes at `/admin/t/{tenant}/findings/{finding}`. Tenant entitlement is enforced per referenced finding before disclosure and before claim. Non-members or out-of-scope users continue to receive `404`. Workspace members with at least one currently viewable findings scope may open the queue shell, but rows, counts, tenant filter values, and empty-state hints remain derived only from currently authorized tenant findings. Workspace members with no currently viewable findings scope for intake anywhere in the workspace receive `403` on queue access. Members with findings view but lacking findings assign capability may inspect rows but must receive `403` on claim attempts. No raw capability strings, role checks, or second permission system may be introduced. Global search behavior is unchanged. **Constitution alignment (UI-FIL-001):** The intake queue must use native Filament page, table, filter, badge, action, notification, and empty-state primitives or existing shared UI helpers. No page-local badge markup, local color language, or custom task-board chrome may be introduced for queue semantics. **Constitution alignment (UI-NAMING-001):** The canonical operator-facing vocabulary is `Findings intake`, `Unassigned`, `Needs triage`, `Claim finding`, and `Open my findings`. The page is about findings workflow, not a generic task engine. Terms such as `task`, `work item`, or `queue record` must not replace the finding domain language in primary labels. **Constitution alignment (DECIDE-001):** The intake queue is a primary decision surface because it answers the team's first-routing question in one place before work becomes personal. `My Findings` remains the personal execution surface. Default-visible content must be enough to choose whether to claim or open a finding without reconstructing tenant context elsewhere. **Constitution alignment (UI-CONST-001 / UI-SURF-001 / ACTSURF-001 / HDR-001):** The intake queue has exactly one primary inspect model: the finding. Row click is required. There is no redundant `View` action. The only visible row mutation shortcut in v1 is `Claim`, because it is the smallest safe handoff into personal execution. Dangerous lifecycle actions remain on the existing tenant finding detail and tenant findings list rather than moving into the queue. **Constitution alignment (OPSURF-001):** Default-visible queue content must stay operator-first: finding summary, tenant, severity, lifecycle state, due urgency, owner context, and intake reason before diagnostics. The `Claim` action is a `TenantPilot only` mutation on assignment metadata and does not imply provider-side change. Raw evidence, run context, and exception history remain secondary behind finding detail. **Constitution alignment (UI-SEM-001 / LAYER-001 / TEST-TRUTH-001):** Direct reuse of a tenant-local `Unassigned` filter is insufficient because it does not create one workspace-safe shared intake surface or a clear handoff into `My Findings`. This feature still avoids new semantic infrastructure by deriving intake directly from existing open-status, owner, assignee, due-date, and entitlement truth. Tests must prove the business consequences: shared visibility, pre-assignment boundaries, claim safety, and handoff continuity. ### Functional Requirements - **FR-001**: The system MUST provide a canonical shared findings intake queue at `/admin/findings/intake` for the current workspace member. - **FR-002**: The intake queue MUST include only findings that are in an open workflow status, have no current assignee, and belong to tenants the current user is entitled and currently authorized to inspect. - **FR-003**: Open workflow status for intake MUST reuse the existing findings contract from Spec 111: `new`, `triaged`, `in_progress`, and `reopened`. - **FR-004**: The intake queue MUST expose fixed queue views for `Unassigned` and `Needs triage`. `Unassigned` includes all visible intake rows. `Needs triage` is a strict subset limited to visible intake rows in `new` or `reopened` status. - **FR-005**: Queue rows MUST show at minimum the tenant, finding summary, severity, lifecycle status, due date or overdue state, owner when present, and why the finding is still in intake. - **FR-006**: Findings that already have an assignee MUST NOT appear in intake, including overdue or reopened findings. Assigned work remains in personal or tenant-local execution surfaces. - **FR-007**: When an active tenant context exists, the intake queue MUST apply that tenant as a default prefilter and allow the operator to clear only that tenant prefilter to return to all visible tenants. - **FR-008**: Intake rows, counts, tenant filter values, and queue summary state MUST be derived only from findings the current user is entitled and currently authorized to inspect and MUST NOT leak hidden or capability-blocked tenant scope through labels, counts, filter values, or empty-state hints. - **FR-009**: Authorized operators with the existing findings assign capability MUST be able to claim an intake item through a lightweight pre-commit preview and explicit confirmation step. Claim sets `assignee = current user`, leaves owner and workflow status unchanged, and writes an audit entry for the assignment change. - **FR-010**: Successful claim MUST remove the finding from the shared intake queue immediately and provide a clear next step into the existing `My Findings` route or the finding detail view. - **FR-011**: Members who can inspect findings but lack assign capability MAY open rows from intake but MUST NOT be able to claim them. Server-side enforcement remains `403` for in-scope members lacking capability and `404` for non-members or out-of-scope users. - **FR-012**: Opening a row from intake MUST navigate to the existing tenant finding detail for the correct tenant and preserve a return path back to intake. - **FR-013**: The intake queue MUST render calm empty states. If the active tenant prefilter alone causes the queue to become empty while other visible tenants still contain intake items, the empty-state CTA MUST clear the tenant prefilter. If no visible intake items exist at all, the empty state MUST explain that shared backlog is clear and offer one clear CTA into `My Findings`. - **FR-014**: Claim attempts MUST re-check the current assignee server-side at mutation time and MUST NOT silently overwrite another operator's successful claim. - **FR-015**: The feature MUST reuse the owner-versus-assignee contract from Spec 219 and MUST NOT introduce a team model, queue-specific lifecycle states, new ownership roles, or a second permission family. - **FR-016**: Default queue ordering MUST surface urgent intake first: overdue rows first, then `reopened`, then `new`, then remaining unassigned backlog. Within each bucket, rows with due dates sort by `due_at` ascending, rows without due dates sort last, and remaining ties sort by finding ID descending. ## UI Action Matrix *(mandatory when Filament is changed)* | Surface | Location | Header Actions | Inspect Affordance (List/Table) | Row Actions (max 2 visible) | Bulk Actions (grouped) | Empty-State CTA(s) | View Header Actions | Create/Edit Save+Cancel | Audit log? | Notes / Exemptions | |---|---|---|---|---|---|---|---|---|---|---| | Findings intake queue | `/admin/findings/intake` | `Clear tenant filter` only when an active tenant prefilter is applied | Full-row open to `/admin/t/{tenant}/findings/{finding}` | `Claim` only when the current user may assign and the finding is still unassigned, with lightweight preview and explicit confirmation before execution | none | `Clear tenant filter` for tenant-prefilter-empty state; otherwise `Open my findings` | n/a | n/a | yes for successful claim | Action Surface Contract satisfied. One inspect model only, no redundant `View`, no dangerous queue actions, and no empty action groups. | ### Key Entities *(include if feature involves data)* - **Intake finding**: An open tenant-owned finding with no current assignee that the current user is entitled and authorized to inspect. - **Findings intake queue**: A derived canonical shared queue over intake findings that emphasizes first routing before personal execution. - **Needs triage view**: The strict subset of intake findings that are still in `new` or `reopened` status and therefore need first routing attention. ## Success Criteria *(mandatory)* ### Measurable Outcomes - **SC-001**: In acceptance review, an operator can determine within 10 seconds from `/admin/findings/intake` whether visible unassigned findings exist and whether the current tenant filter is narrowing the queue. - **SC-002**: 100% of covered automated tests show only visible unassigned open findings in the intake queue and exclude all assigned, terminal, hidden-tenant, or capability-blocked rows. - **SC-003**: 100% of covered automated tests show that `Needs triage` contains only `new` and `reopened` intake rows while `Unassigned` continues to show the full visible intake backlog. - **SC-004**: From the intake queue, an authorized operator can claim an eligible finding in one short confirmed flow and the finding leaves shared intake immediately without losing a clear next step into personal execution. ## Assumptions - Spec 111 remains the authoritative contract for findings open-status semantics and due-state behavior. - Spec 219 remains the authoritative contract for owner-versus-assignee meaning. - Spec 221 remains the canonical personal destination for claimed finding work. - The existing tenant finding detail remains the canonical deeper workflow surface for triage, reassignment, resolve, close, and exception actions beyond the new intake claim shortcut. ## Non-Goals - Introduce a full team workboard, team metrics, or capacity planning - Add notifications, reminders, or escalation from the intake queue - Add bulk claim, bulk triage, or a second mutation lane on the intake surface - Introduce automatic routing, load balancing, delegation, or fallback-to-role logic - Reframe the findings domain as a generic task or ticketing system ## Dependencies - Spec 111, Findings Workflow V2 + SLA, remains the lifecycle and open-status baseline. - Spec 219, Finding Ownership Semantics Clarification, remains the accountability and assignment baseline. - Spec 221, Findings Operator Inbox V1, remains the personal execution destination after claim.