TenantAtlas/specs/222-findings-intake-team-queue/contracts/findings-intake-team-queue.logical.openapi.yaml
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

470 lines
13 KiB
YAML

openapi: 3.1.0
info:
title: Findings Intake & Team Queue Surface Contract
version: 1.1.0
description: >-
Internal reference contract for the canonical Findings intake queue,
the narrow Claim finding shortcut, and intake continuity into tenant
finding detail and My Findings. The application continues to return
rendered HTML through Filament and Livewire. The vendor media types
below document the structured page and action models that must be
derivable before rendering. This is not a public API commitment.
paths:
/admin/findings/intake:
get:
summary: Canonical shared findings intake queue
description: >-
Returns the rendered admin-plane intake queue for visible unassigned
findings in the current workspace. The page always keeps the fixed
intake scope and may apply an active-tenant prefilter. Queue views are
limited to Unassigned and Needs triage.
responses:
'302':
description: Redirects into the existing workspace chooser flow when workspace context is not yet established
'200':
description: Rendered Findings intake page
content:
text/html:
schema:
type: string
application/vnd.tenantpilot.findings-intake+json:
schema:
$ref: '#/components/schemas/FindingsIntakePage'
'403':
description: Workspace membership exists but no currently viewable findings scope exists for intake disclosure anywhere in the workspace
'404':
description: Workspace scope is not visible because membership is missing or out of scope
/admin/findings/intake/{finding}/claim:
post:
summary: Claim a visible intake finding into personal execution
description: >-
Logical contract for the Livewire-backed Claim finding row action after
the operator has reviewed the lightweight preview/confirmation content.
The server must re-check entitlement, assign capability, and current
assignee under lock before mutating the record.
parameters:
- name: finding
in: path
required: true
schema:
type: integer
responses:
'200':
description: Claim succeeded and the finding left intake
content:
application/vnd.tenantpilot.findings-intake-claim+json:
schema:
$ref: '#/components/schemas/ClaimFindingResult'
'403':
description: Viewer is in scope but lacks the existing findings assign capability
'404':
description: Workspace or tenant scope is not visible for the referenced finding
'409':
description: The row is no longer claimable because another operator claimed it first or it otherwise left intake scope before mutation
/admin/t/{tenant}/findings/{finding}:
get:
summary: Tenant finding detail with intake continuity support
description: >-
Returns the rendered tenant finding detail page. The logical contract
below documents only the continuity inputs required when the page is
opened from Findings intake.
parameters:
- name: tenant
in: path
required: true
schema:
type: integer
- name: finding
in: path
required: true
schema:
type: integer
- name: nav
in: query
required: false
style: deepObject
explode: true
schema:
$ref: '#/components/schemas/CanonicalNavigationContext'
responses:
'200':
description: Rendered tenant finding detail page
content:
text/html:
schema:
type: string
application/vnd.tenantpilot.finding-detail-from-intake+json:
schema:
$ref: '#/components/schemas/FindingDetailContinuation'
'403':
description: Viewer is in scope but lacks the existing findings capability for the tenant detail destination
'404':
description: Tenant or finding is not visible because workspace or tenant entitlement is missing
components:
schemas:
FindingsIntakePage:
type: object
required:
- header
- appliedScope
- queueViews
- summaryCounts
- rows
- emptyState
properties:
header:
$ref: '#/components/schemas/IntakeHeader'
appliedScope:
$ref: '#/components/schemas/IntakeAppliedScope'
queueViews:
type: array
items:
$ref: '#/components/schemas/QueueViewDefinition'
summaryCounts:
$ref: '#/components/schemas/IntakeSummaryCounts'
rows:
description: >-
Rows are ordered overdue first, reopened second, new third, then
remaining unassigned backlog. Within each bucket, rows with due
dates sort by dueAt ascending, rows without due dates sort last,
and remaining ties sort by findingId descending.
type: array
items:
$ref: '#/components/schemas/IntakeFindingRow'
emptyState:
oneOf:
- $ref: '#/components/schemas/IntakeEmptyState'
- type: 'null'
IntakeHeader:
type: object
required:
- title
- description
properties:
title:
type: string
enum:
- Findings intake
description:
type: string
clearTenantFilterAction:
oneOf:
- $ref: '#/components/schemas/ActionLink'
- type: 'null'
IntakeAppliedScope:
type: object
required:
- workspaceScoped
- fixedScope
- queueView
- tenantPrefilterSource
properties:
workspaceScoped:
type: boolean
fixedScope:
type: string
enum:
- visible_unassigned_open_findings_only
queueView:
type: string
enum:
- unassigned
- needs_triage
tenantPrefilterSource:
type: string
enum:
- active_tenant_context
- explicit_filter
- none
tenantLabel:
type:
- string
- 'null'
QueueViewDefinition:
type: object
required:
- key
- label
- fixed
- active
properties:
key:
type: string
enum:
- unassigned
- needs_triage
label:
type: string
fixed:
type: boolean
active:
type: boolean
badgeCount:
type:
- integer
- 'null'
minimum: 0
IntakeSummaryCounts:
type: object
description: Counts derived only from visible intake rows.
required:
- visibleUnassigned
- visibleNeedsTriage
- visibleOverdue
properties:
visibleUnassigned:
type: integer
minimum: 0
visibleNeedsTriage:
type: integer
minimum: 0
visibleOverdue:
type: integer
minimum: 0
IntakeFindingRow:
type: object
required:
- findingId
- tenantId
- tenantLabel
- summary
- severity
- status
- intakeReason
- dueState
- detailUrl
properties:
findingId:
type: integer
tenantId:
type: integer
tenantLabel:
type: string
summary:
type: string
severity:
$ref: '#/components/schemas/Badge'
status:
$ref: '#/components/schemas/Badge'
dueAt:
type:
- string
- 'null'
format: date-time
dueState:
$ref: '#/components/schemas/DueState'
ownerLabel:
type:
- string
- 'null'
intakeReason:
type: string
enum:
- Unassigned
- Needs triage
claimAction:
oneOf:
- $ref: '#/components/schemas/ClaimFindingAffordance'
- type: 'null'
detailUrl:
type: string
navigationContext:
oneOf:
- $ref: '#/components/schemas/CanonicalNavigationContext'
- type: 'null'
ClaimFindingAffordance:
type: object
required:
- actionId
- label
- enabled
- requiresConfirmation
properties:
actionId:
type: string
enum:
- claim
label:
type: string
enum:
- Claim finding
enabled:
type: boolean
requiresConfirmation:
type: boolean
enum:
- true
confirmationTitle:
type:
- string
- 'null'
confirmationBody:
type:
- string
- 'null'
disabledReason:
type:
- string
- 'null'
DueState:
type: object
required:
- label
- tone
properties:
label:
type: string
tone:
type: string
enum:
- calm
- warning
- danger
IntakeEmptyState:
type: object
required:
- title
- body
- action
properties:
title:
type: string
body:
type: string
reason:
type: string
enum:
- no_visible_intake_work
- active_tenant_prefilter_excludes_rows
action:
oneOf:
- $ref: '#/components/schemas/OpenMyFindingsActionLink'
- $ref: '#/components/schemas/ClearTenantFilterActionLink'
ClaimFindingResult:
type: object
required:
- findingId
- tenantId
- assigneeUserId
- auditActionId
- queueOutcome
- nextPrimaryAction
properties:
findingId:
type: integer
tenantId:
type: integer
assigneeUserId:
type: integer
auditActionId:
type: string
enum:
- finding.assigned
queueOutcome:
type: string
enum:
- removed_from_intake
nextPrimaryAction:
$ref: '#/components/schemas/OpenMyFindingsActionLink'
nextInspectAction:
oneOf:
- $ref: '#/components/schemas/OpenFindingActionLink'
- type: 'null'
FindingDetailContinuation:
type: object
description: >-
Continuity payload for tenant finding detail when it is opened from the
Findings intake queue. The backLink is present whenever canonical intake
navigation context is provided and may be null only for direct entry
without intake continuity context.
required:
- findingId
- tenantId
properties:
findingId:
type: integer
tenantId:
type: integer
backLink:
oneOf:
- $ref: '#/components/schemas/BackToFindingsIntakeActionLink'
- type: 'null'
CanonicalNavigationContext:
type: object
required:
- source_surface
- canonical_route_name
properties:
source_surface:
type: string
canonical_route_name:
type: string
tenant_id:
type:
- integer
- 'null'
back_label:
type:
- string
- 'null'
back_url:
type:
- string
- 'null'
ActionLink:
type: object
required:
- label
- url
properties:
label:
type: string
url:
type: string
OpenMyFindingsActionLink:
allOf:
- $ref: '#/components/schemas/ActionLink'
- type: object
properties:
label:
type: string
enum:
- Open my findings
OpenFindingActionLink:
allOf:
- $ref: '#/components/schemas/ActionLink'
- type: object
properties:
label:
type: string
enum:
- Open finding
ClearTenantFilterActionLink:
allOf:
- $ref: '#/components/schemas/ActionLink'
- type: object
properties:
label:
type: string
enum:
- Clear tenant filter
BackToFindingsIntakeActionLink:
allOf:
- $ref: '#/components/schemas/ActionLink'
- type: object
properties:
label:
type: string
enum:
- Back to findings intake
Badge:
type: object
required:
- label
properties:
label:
type: string
color:
type:
- string
- 'null'