## 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
22 KiB
Implementation Plan: Findings Intake & Team Queue V1
Branch: 222-findings-intake-team-queue | Date: 2026-04-21 | Spec: /Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/222-findings-intake-team-queue/spec.md
Input: Feature specification from /Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/222-findings-intake-team-queue/spec.md
Note: This plan keeps the work inside the existing admin workspace shell, tenant-owned Finding truth, current tenant finding detail surface, and the already-shipped My Findings destination from Spec 221. The intended implementation is one new canonical /admin page plus one narrow self-claim shortcut. It does not add persistence, a team model, a second permission family, bulk claim, queue automation, or provider-side work.
Summary
Add one canonical admin-plane intake queue at /admin/findings/intake that shows visible unassigned findings across the current workspace, separates Unassigned from the stricter Needs triage subset, and lets an authorized operator claim an item into the existing /admin/findings/my-work surface through a lightweight preview/confirmation flow without opening the broader assignment form first. Reuse the existing MyFindingsInbox page shape, FindingResource badge and navigation helpers, CanonicalAdminTenantFilterState for active-tenant prefiltering, CanonicalNavigationContext for intake-to-detail continuity, and FindingWorkflowService plus the current finding.assigned audit path, while adding one claim-specific stale-row guard so a delayed click cannot silently overwrite another operator's successful claim.
Technical Context
Language/Version: PHP 8.4.15, Laravel 12, Filament v5, Livewire v4, Blade
Primary Dependencies: Filament admin pages/tables/actions/notifications, Finding, FindingResource, FindingWorkflowService, FindingPolicy, CapabilityResolver, CanonicalAdminTenantFilterState, CanonicalNavigationContext, WorkspaceContext, and UiEnforcement
Storage: PostgreSQL via existing findings, tenants, tenant_memberships, audit_logs, and workspace session context; no schema changes planned
Testing: Pest v4 feature tests with Livewire/Filament assertions; existing action-surface and Filament guard suites remain ambient CI protection
Validation Lanes: fast-feedback, confidence
Target Platform: Dockerized Laravel web application via Sail locally and Linux containers in deployment
Project Type: Laravel monolith inside the wt-plattform monorepo
Performance Goals: Keep intake rendering and claim DB-only, eager-load tenant and owner display context, avoid N+1 capability lookups across visible tenants, and keep first operator scan within the 10-second acceptance target
Constraints: No Graph calls, no new OperationRun, no new capabilities, no new persistence, no hidden-tenant leakage, no bulk claim, no new assets, no silent overwrite on stale claim attempts, and no expansion of lifecycle or ownership semantics
Scale/Scope: One admin page and Blade view, one derived intake query, one narrow workflow-service extension or guard for claim conflict handling, three focused feature suites, and ambient compliance with existing action-surface and Filament table guards
UI / Surface Guardrail Plan
- Guardrail scope: changed surfaces
- Native vs custom classification summary: native Filament page, table, tab, badge, row-action, notification, and empty-state primitives only
- Shared-family relevance: findings workflow family and canonical admin findings pages
- State layers in scope: shell, page, detail, URL-query
- Handling modes by drift class or surface: review-mandatory
- Repository-signal treatment: review-mandatory
- Special surface test profiles: global-context-shell
- Required tests or manual smoke: functional-core, state-contract
- Exception path and spread control: none; the queue stays list-first with one safe inline shortcut while dangerous lifecycle actions remain on existing tenant detail and tenant findings list surfaces
- Active feature PR close-out entry: Guardrail
Constitution Check
GATE: Passed before Phase 0 research. Re-check after Phase 1 design.
| Principle | Pre-Research | Post-Design | Notes |
|---|---|---|---|
| Inventory-first / snapshots-second | PASS | PASS | The queue remains a live derived view over Finding truth only; no snapshot or backup semantics are introduced |
| Read/write separation | PASS | PASS | The only write is the narrow Claim assignment shortcut; it uses a lightweight preview and explicit confirmation step, is TenantPilot-only, audit-logged, race-checked, and covered by focused tests while dangerous workflow mutations remain on existing tenant detail surfaces |
| Graph contract path | PASS | PASS | No Graph client, contract-registry, or provider API work is added |
| Deterministic capabilities / RBAC-UX | PASS | PASS | Workspace membership plus at least one currently viewable findings scope gates the admin page, per-tenant findings visibility gates rows and counts once inside the queue, TENANT_FINDINGS_ASSIGN gates claim, non-members stay 404, workspace members with no currently viewable findings scope get 403, and in-scope members missing claim capability remain 403 on execution |
| Workspace / tenant isolation | PASS | PASS | The admin queue is workspace-scoped, row disclosure remains tenant-entitlement checked, and drilldown stays on /admin/t/{tenant}/findings/{finding} with tenant-safe continuity |
| Run observability / Ops-UX | PASS | PASS | No long-running work, no OperationRun, and no queue/progress surface changes are introduced; the short DB-only claim write is audit-logged |
| Proportionality / no premature abstraction | PASS | PASS | The intake query stays page-local and the claim behavior extends existing workflow seams instead of creating a new queue framework or team-routing abstraction |
| Persisted truth / few layers | PASS | PASS | Intake views, queue reason, and summary counts remain derived from existing finding status, assignee, owner, due-date, and entitlement truth |
| Behavioral state discipline | PASS | PASS | Unassigned and Needs triage remain derived queue views; no new persisted status, reason family, or ownership role is introduced |
| Badge semantics (BADGE-001) | PASS | PASS | Severity and lifecycle cues reuse existing findings badge rendering and due-attention helpers |
| Filament-native UI (UI-FIL-001) | PASS | PASS | The design stays within Filament page, table, tabs, row actions, notifications, and empty-state primitives |
| Action surface / inspect model | PASS | PASS | Row click remains the primary inspect model, Claim is the only inline safe shortcut, and there is no redundant View or bulk action lane |
| Decision-first / OPSURF | PASS | PASS | The intake queue is the primary decision surface for pre-assignment routing and keeps diagnostics secondary behind finding detail |
| Test governance (TEST-GOV-001) | PASS | PASS | Proof remains in three focused feature suites with explicit lane fit and no new heavy-governance or browser family |
| Filament v5 / Livewire v4 compliance | PASS | PASS | The feature remains inside Filament v5 patterns and Livewire v4-compatible page/table behavior only |
| Provider registration / global search / assets | PASS | PASS | Panel providers already live in apps/platform/bootstrap/providers.php; the new page only extends AdminPanelProvider, FindingResource global search is unchanged and already has a View page, and no new assets are introduced so the deploy filament:assets step is unchanged |
Test Governance Check
- Test purpose / classification by changed surface:
Featurefor the admin intake page, authorization boundaries, and claim handoff behavior - Affected validation lanes:
fast-feedback,confidence - Why this lane mix is the narrowest sufficient proof: The feature risk is visible queue truth, tenant-safe filtering,
Needs triagesubset semantics, claim authorization, stale-row conflict handling, and handoff into an existing personal queue. Focused feature tests prove that integrated behavior without adding unit seams, browser coverage, or heavy-governance breadth. - Narrowest proving command(s):
cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingsIntakeQueueTest.php tests/Feature/Authorization/FindingsIntakeAuthorizationTest.phpcd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingsClaimHandoffTest.php
- Fixture / helper / factory / seed / context cost risks: Moderate. The tests need one workspace, multiple visible and hidden tenants, owner-versus-assignee combinations, unassigned and assigned findings across open and terminal states, explicit active-tenant session context, and one stale-claim race scenario.
- Expensive defaults or shared helper growth introduced?: no; the new suites should reuse
createUserWithTenant(...)andFinding::factory()and keep any intake-specific helper local to the new tests - Heavy-family additions, promotions, or visibility changes: none
- Surface-class relief / special coverage rule: named special profile
global-context-shellis required because the page depends on workspace context, active-tenant prefilter continuity, tenant-safe detail drilldown, and row-level capability visibility - Closing validation and reviewer handoff: Reviewers should rely on the exact commands above and verify that hidden-tenant or capability-blocked findings never leak into rows, counts, tab badges, or tenant filter values;
acknowledgedfindings stay out of intake;Needs triageremains limited tonewandreopened; claim leaves owner and workflow status unchanged; and stale claims fail honestly instead of overwriting another operator's assignment. - Budget / baseline / trend follow-up: none
- Review-stop questions: Did the intake query accidentally include assigned or
acknowledgedrows? Did claim mutate owner or lifecycle state? Did a second inline action or bulk lane appear? Did any new shared queue abstraction or team model appear without a second real consumer? Did stale-claim behavior degrade into silent overwrite? - Escalation path: document-in-feature unless a second shared intake surface or reusable cross-tenant queue framework is introduced, in which case follow-up-spec or split
- Active feature PR close-out entry: Guardrail
- Why no dedicated follow-up spec is needed: The feature remains bounded to one derived queue surface and one narrow claim path, with no new persistence, no new workflow family, and no structural test-cost center
Project Structure
Documentation (this feature)
specs/222-findings-intake-team-queue/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ └── findings-intake-team-queue.logical.openapi.yaml
├── checklists/
│ └── requirements.md
└── tasks.md
Source Code (repository root)
apps/platform/
├── app/
│ ├── Filament/
│ │ ├── Pages/
│ │ │ └── Findings/
│ │ │ └── FindingsIntakeQueue.php
│ │ └── Resources/
│ │ └── FindingResource.php
│ ├── Providers/
│ │ └── Filament/
│ │ └── AdminPanelProvider.php
│ └── Services/
│ └── Findings/
│ └── FindingWorkflowService.php
├── resources/
│ └── views/
│ └── filament/
│ └── pages/
│ └── findings/
│ └── findings-intake-queue.blade.php
└── tests/
└── Feature/
├── Authorization/
│ └── FindingsIntakeAuthorizationTest.php
└── Findings/
├── FindingsClaimHandoffTest.php
└── FindingsIntakeQueueTest.php
Structure Decision: Standard Laravel monolith. The feature stays inside the existing admin panel provider, finding domain model, workflow service, and focused Pest feature suites. No new base directory, package, or persisted model is required.
Complexity Tracking
| Violation | Why Needed | Simpler Alternative Rejected Because |
|---|---|---|
| none | — | — |
Proportionality Review
- Current operator problem: Unassigned findings exist in the current workflow, but there is no single trustworthy workspace-safe queue for the pre-assignment backlog.
- Existing structure is insufficient because: Tenant-local findings lists and ad hoc filters still force tenant hopping and do not provide a clean handoff from shared backlog into the existing personal queue.
- Narrowest correct implementation: Add one admin intake page and one safe self-claim shortcut derived directly from existing finding lifecycle, due-state, ownership, and entitlement truth.
- Ownership cost created: One page/view pair, one small claim conflict guard inside the existing workflow service seam, and three focused feature suites.
- Alternative intentionally rejected: A broader team workboard, notification-first routing model, or queue framework was rejected because those shapes add durable workflow machinery before this smaller intake slice is proven useful.
- Release truth: Current-release truth. The work operationalizes an already-existing unassigned backlog now rather than preparing a later team-orchestration system.
Phase 0 Research
Research outcomes are captured in /Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/222-findings-intake-team-queue/research.md.
Key decisions:
- Implement the intake queue as a new Filament admin page with slug
findings/intakeunder the existingAdminPanelProvider, not as a tenant resource variant and not as a standalone controller route. - Keep queue truth as a direct
Findingquery scoped by workspace, visible tenant IDs,assignee_user_id IS NULL, andFinding::openStatuses()rather thanFinding::openStatusesForQuery(), becauseacknowledgedrows are intentionally excluded from intake. - Model
UnassignedandNeeds triageas fixed queue views inside the page shell, not as a new taxonomy, persisted preference, or generic filter framework. - Reuse
CanonicalAdminTenantFilterStatefor the active-tenant prefilter andCanonicalNavigationContextfor intake-to-detail continuity andBack to findings intakebehavior. - Add
Claim findingas a narrow row action that reuses the existing findings assign capability, success notification patterns, lightweight preview/confirmation content, andfinding.assignedaudit trail, but adds a claim-specific stale-row guard so delayed clicks fail honestly when another operator claimed the row first. - Prove the feature with three focused Pest feature suites while relying on existing action-surface and Filament-table guards as ambient CI coverage.
Phase 1 Design
Design artifacts are created under /Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/222-findings-intake-team-queue/:
research.md: routing, query, fixed-view, claim, and continuity decisionsdata-model.md: existing entities plus derived intake row, queue state, and claim outcome projectionscontracts/findings-intake-team-queue.logical.openapi.yaml: internal logical contract for intake rendering, claim, and continuity inputsquickstart.md: focused validation workflow for implementation and review
Design decisions:
- No schema migration is required; the queue, view counts, and empty states remain fully derived.
- The canonical implementation seam is one new admin page plus one narrow workflow-service claim guard and lightweight preview/confirmation surface, not a new shared queue or team-routing subsystem.
- Active tenant context remains canonical through
CanonicalAdminTenantFilterState, while detail continuity remains canonical throughCanonicalNavigationContext. - Claim remains a TenantPilot-only assignee mutation. It leaves owner and lifecycle state unchanged, uses a lightweight preview/confirmation modal before execution, writes an audit entry, refuses silent overwrite under lock, and points the operator to the existing
My Findingsdestination after success.
Phase 1 Agent Context Update
Run:
.specify/scripts/bash/update-agent-context.sh copilot
Constitution Check — Post-Design Re-evaluation
- PASS — the design stays inside existing admin, finding, and workflow seams with no new persistence, no Graph work, no
OperationRun, no new capability family, and no new assets. - PASS — Livewire v4.0+ and Filament v5 constraints remain satisfied, panel provider registration stays in
apps/platform/bootstrap/providers.php,FindingResourceglobal search remains unchanged and still has a View page, and the non-destructiveClaimaction still uses an explicit lightweight preview/confirmation flow to satisfy write-governance without expanding into a broader assignment form.
Implementation Strategy
Phase A — Add The Canonical Shared Intake Surface
Goal: Create one workspace-scoped intake queue under /admin/findings/intake.
| Step | File | Change |
|---|---|---|
| A.1 | apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php |
Add a new Filament admin page with slug findings/intake, HasTable, workspace membership access checks, explicit 403 handling when no findings-viewable scope exists, visible-tenant resolution, active-tenant sync, and action-surface declaration for row-click inspect plus one safe Claim shortcut |
| A.2 | apps/platform/resources/views/filament/pages/findings/findings-intake-queue.blade.php |
Render the page shell, page description, fixed Unassigned and Needs triage view controls, native Filament table, and calm empty-state branches with either Clear tenant filter or Open my findings |
| A.3 | apps/platform/app/Providers/Filament/AdminPanelProvider.php |
Register the new page in the existing admin panel; do not move provider registration because it already lives in apps/platform/bootstrap/providers.php |
Phase B — Derive Intake Truth From Existing Finding Semantics
Goal: Keep inclusion, queue reason, and ordering aligned with Specs 111, 219, and 221.
| Step | File | Change |
|---|---|---|
| B.1 | apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php |
Build the base query from Finding, restricted to the current workspace, visible tenant IDs, assignee_user_id IS NULL, and Finding::openStatuses() rather than openStatusesForQuery() |
| B.2 | apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php and apps/platform/app/Filament/Resources/FindingResource.php |
Reuse existing severity, lifecycle, due-attention, and owner display helpers; derive queue reason as Needs triage for new and reopened, otherwise Unassigned |
| B.3 | apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php |
Add visible summary counts, fixed queue-view metadata, capability-safe tenant filter options, and deterministic urgency ordering: overdue first, then reopened, then new, then remaining unassigned backlog, with due-date and finding-ID tie breaks |
Phase C — Add The Safe Claim Shortcut Without Expanding Workflow Scope
Goal: Turn shared backlog into personal work in one short confirmed flow without inventing a second assignment system.
| Step | File | Change |
|---|---|---|
| C.1 | apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php |
Add one inline Claim finding row action using UiEnforcement, lightweight preview/confirmation content, preserved visibility for in-scope members, and success/failure notifications that keep the queue honest |
| C.2 | apps/platform/app/Services/Findings/FindingWorkflowService.php |
Add a claim-specific path or guard that reuses existing assignment semantics and AuditActionId::FindingAssigned, but under lock refuses any record whose assignee is no longer null before mutation |
| C.3 | apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php |
After confirmed claim success, refresh the queue so the row disappears immediately and provide a clear next step into My Findings or the tenant finding detail without changing owner or workflow state |
Phase D — Preserve Canonical Context And Continuity
Goal: Make intake behave like a first-class admin-plane queue rather than a detached filter.
| Step | File | Change |
|---|---|---|
| D.1 | apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php |
Reuse CanonicalAdminTenantFilterState so the active tenant becomes the default prefilter and can be cleared without dropping the fixed intake scope or queue view |
| D.2 | apps/platform/app/Filament/Pages/Findings/FindingsIntakeQueue.php |
Build row URLs to tenant finding detail with CanonicalNavigationContext carrying Back to findings intake continuity |
| D.3 | Existing tenant finding detail seam | Keep the existing tenant detail page as the deeper workflow surface and ensure it continues to consume navigation context without queue-specific detail forks |
Phase E — Protect Visibility, Claim Safety, And Handoff Truth
Goal: Lock down queue inclusion, authorization, conflict handling, and continuity with focused regression coverage.
| Step | File | Change |
|---|---|---|
| E.1 | apps/platform/tests/Feature/Findings/FindingsIntakeQueueTest.php |
Cover visible unassigned rows only, active-tenant prefilter behavior, fixed queue views, queue reason rendering, owner-context rendering, assigned and acknowledged exclusion, hidden-tenant suppression, deterministic ordering, empty states, and intake-to-detail continuity |
| E.2 | apps/platform/tests/Feature/Authorization/FindingsIntakeAuthorizationTest.php |
Cover workspace context recovery, non-member 404, queue-access 403 when no currently viewable findings scope exists, disabled or rejected claim for members missing assign capability, and tenant-safe detail disclosure |
| E.3 | apps/platform/tests/Feature/Findings/FindingsClaimHandoffTest.php |
Cover claim preview/confirmation rendering, successful claim, audit side effects, immediate queue removal, My Findings next-step alignment, and stale-row conflict refusal when another operator claims first |
| E.4 | cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent plus the focused Pest commands above |
Run formatting and the narrowest proving suites before closing implementation |