238 lines
22 KiB
Markdown
238 lines
22 KiB
Markdown
# 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**: `Feature` for 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 triage` subset 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.php`
|
|
- `cd 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(...)` and `Finding::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-shell` is 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; `acknowledged` findings stay out of intake; `Needs triage` remains limited to `new` and `reopened`; 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 `acknowledged` rows? 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)
|
|
|
|
```text
|
|
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)
|
|
|
|
```text
|
|
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/intake` under the existing `AdminPanelProvider`, not as a tenant resource variant and not as a standalone controller route.
|
|
- Keep queue truth as a direct `Finding` query scoped by workspace, visible tenant IDs, `assignee_user_id IS NULL`, and `Finding::openStatuses()` rather than `Finding::openStatusesForQuery()`, because `acknowledged` rows are intentionally excluded from intake.
|
|
- Model `Unassigned` and `Needs triage` as fixed queue views inside the page shell, not as a new taxonomy, persisted preference, or generic filter framework.
|
|
- Reuse `CanonicalAdminTenantFilterState` for the active-tenant prefilter and `CanonicalNavigationContext` for intake-to-detail continuity and `Back to findings intake` behavior.
|
|
- Add `Claim finding` as a narrow row action that reuses the existing findings assign capability, success notification patterns, lightweight preview/confirmation content, and `finding.assigned` audit 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 decisions
|
|
- `data-model.md`: existing entities plus derived intake row, queue state, and claim outcome projections
|
|
- `contracts/findings-intake-team-queue.logical.openapi.yaml`: internal logical contract for intake rendering, claim, and continuity inputs
|
|
- `quickstart.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 through `CanonicalNavigationContext`.
|
|
- 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 Findings` destination 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`, `FindingResource` global search remains unchanged and still has a View page, and the non-destructive `Claim` action 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 |
|