TenantAtlas/specs/231-finding-outcome-taxonomy/contracts/finding-outcome-taxonomy.logical.openapi.yaml
ahmido 421261a517
Some checks failed
Main Confidence / confidence (push) Failing after 48s
feat: implement finding outcome taxonomy (#267)
## Summary
- implement the finding outcome taxonomy end-to-end with canonical resolve, close, reopen, and verification semantics
- align finding UI, filters, audit metadata, review summaries, and export/read-model consumers to the shared outcome semantics
- add focused Pest coverage and complete the spec artifacts for feature 231

## Details
- manual resolve is limited to the canonical `remediated` outcome
- close and reopen flows now use bounded canonical reasons
- trusted system clear and reopen distinguish verified-clear from verification-failed and recurrence paths
- duplicate lifecycle backfill now closes findings canonically as `duplicate`
- accepted-risk recording now uses the canonical `accepted_risk` reason
- finding detail and list surfaces now expose terminal outcome and verification summaries
- review, snapshot, and review-pack consumers now propagate the same outcome buckets

## Filament / Platform Contract
- Livewire v4.0+ compatibility remains intact
- provider registration is unchanged and remains in `bootstrap/providers.php`
- no new globally searchable resource was introduced; `FindingResource` still has a View page and `TenantReviewResource` remains globally searchable false
- lifecycle mutations still run through confirmed Filament actions with capability enforcement
- no new asset family was added; the existing `filament:assets` deploy step is unchanged

## Verification
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/FindingWorkflowServiceTest.php tests/Feature/Findings/FindingRecurrenceTest.php tests/Feature/Findings/FindingsListFiltersTest.php tests/Feature/Filament/FindingResolvedReferencePresentationTest.php tests/Feature/Findings/FindingOutcomeSummaryReportingTest.php tests/Feature/Findings/FindingRiskGovernanceProjectionTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings tests/Feature/Filament/FindingResolvedReferencePresentationTest.php tests/Feature/Models/FindingResolvedTest.php tests/Unit/Findings/FindingWorkflowServiceTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/TenantReview/TenantReviewExplanationSurfaceTest.php tests/Feature/TenantReview/TenantReviewRegisterTest.php tests/Feature/ReviewPack/TenantReviewDerivedReviewPackTest.php`
- browser smoke: `/admin/findings/my-work` -> finding detail resolve flow -> queue regression check passed

## Notes
- this commit also includes the existing `.github/agents/copilot-instructions.md` workspace change that was already present in the worktree when all changes were committed

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #267
2026-04-23 07:29:05 +00:00

397 lines
10 KiB
YAML

openapi: 3.1.0
info:
title: Finding Outcome Taxonomy Logical Contract
version: 0.1.0
description: |
Logical contract for the findings outcome taxonomy feature. This is not a new
public HTTP API commitment. It documents the request and response shapes that
existing Filament and service workflows must converge on.
servers:
- url: https://tenantpilot.local
description: Logical base URL only
tags:
- name: Findings
- name: FindingsInternal
paths:
/tenants/{tenantId}/findings:
get:
tags: [Findings]
summary: List tenant findings with terminal-outcome filters
operationId: listTenantFindings
parameters:
- $ref: '#/components/parameters/TenantId'
- name: status
in: query
schema:
$ref: '#/components/schemas/FindingStatus'
- name: terminal_outcome
in: query
schema:
$ref: '#/components/schemas/TerminalOutcomeKey'
- name: verification_state
in: query
schema:
$ref: '#/components/schemas/VerificationState'
responses:
'200':
description: Tenant findings list
content:
application/json:
schema:
type: object
required: [data]
properties:
data:
type: array
items:
$ref: '#/components/schemas/FindingSummary'
/tenants/{tenantId}/findings/{findingId}:
get:
tags: [Findings]
summary: Get one finding with current terminal-outcome semantics
operationId: getTenantFinding
parameters:
- $ref: '#/components/parameters/TenantId'
- $ref: '#/components/parameters/FindingId'
responses:
'200':
description: Finding detail
content:
application/json:
schema:
$ref: '#/components/schemas/FindingDetail'
/tenants/{tenantId}/findings/{findingId}/resolve:
post:
tags: [Findings]
summary: Resolve a finding with a bounded operator reason
operationId: resolveTenantFinding
parameters:
- $ref: '#/components/parameters/TenantId'
- $ref: '#/components/parameters/FindingId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ResolveFindingRequest'
responses:
'200':
description: Finding resolved pending verification
content:
application/json:
schema:
$ref: '#/components/schemas/FindingDetail'
/tenants/{tenantId}/findings/{findingId}/close:
post:
tags: [Findings]
summary: Close a finding with a bounded administrative reason
operationId: closeTenantFinding
parameters:
- $ref: '#/components/parameters/TenantId'
- $ref: '#/components/parameters/FindingId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CloseFindingRequest'
responses:
'200':
description: Finding closed with a non-remediation outcome
content:
application/json:
schema:
$ref: '#/components/schemas/FindingDetail'
/tenants/{tenantId}/findings/{findingId}/reopen:
post:
tags: [Findings]
summary: Reopen a terminal finding with a bounded reopen reason
operationId: reopenTenantFinding
parameters:
- $ref: '#/components/parameters/TenantId'
- $ref: '#/components/parameters/FindingId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ReopenFindingRequest'
responses:
'200':
description: Finding reopened
content:
application/json:
schema:
$ref: '#/components/schemas/FindingDetail'
/internal/tenants/{tenantId}/findings/{findingId}/system-clear:
post:
tags: [FindingsInternal]
summary: Apply a trusted system-clear reason
operationId: systemClearFinding
x-internal: true
parameters:
- $ref: '#/components/parameters/TenantId'
- $ref: '#/components/parameters/FindingId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SystemClearFindingRequest'
responses:
'200':
description: Finding moved into a verified-cleared outcome
content:
application/json:
schema:
$ref: '#/components/schemas/FindingDetail'
/internal/tenants/{tenantId}/findings/{findingId}/system-reopen:
post:
tags: [FindingsInternal]
summary: Reopen a terminal finding due to trusted recurrence or verification failure
operationId: systemReopenFinding
x-internal: true
parameters:
- $ref: '#/components/parameters/TenantId'
- $ref: '#/components/parameters/FindingId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SystemReopenFindingRequest'
responses:
'200':
description: Finding reopened by trusted automation
content:
application/json:
schema:
$ref: '#/components/schemas/FindingDetail'
components:
parameters:
TenantId:
name: tenantId
in: path
required: true
schema:
type: integer
FindingId:
name: findingId
in: path
required: true
schema:
type: integer
schemas:
FindingStatus:
type: string
enum:
- new
- acknowledged
- triaged
- in_progress
- reopened
- resolved
- closed
- risk_accepted
VerificationState:
type: string
enum:
- pending_verification
- verified_cleared
- not_applicable
ResolveReasonKey:
type: string
enum:
- remediated
- no_longer_drifting
- permission_granted
- permission_removed_from_registry
- role_assignment_removed
- ga_count_within_threshold
CloseReasonKey:
type: string
enum:
- false_positive
- duplicate
- no_longer_applicable
- accepted_risk
ReopenReasonKey:
type: string
enum:
- recurred_after_resolution
- verification_failed
- manual_reassessment
TerminalOutcomeKey:
type: string
enum:
- resolved_pending_verification
- verified_cleared
- closed_false_positive
- closed_duplicate
- closed_no_longer_applicable
- risk_accepted
TerminalOutcome:
type: object
required:
- key
- label
- verification_state
- report_bucket
properties:
key:
$ref: '#/components/schemas/TerminalOutcomeKey'
label:
type: string
verification_state:
$ref: '#/components/schemas/VerificationState'
report_bucket:
type: string
enum:
- remediation_pending_verification
- remediation_verified
- administrative_closure
- accepted_risk
governance_state:
type: string
nullable: true
description: Present only when the outcome depends on risk-governance validity.
ResolveFindingRequest:
type: object
required: [reason]
properties:
reason:
type: string
enum: [remediated]
note:
type: string
maxLength: 255
nullable: true
CloseFindingRequest:
type: object
required: [reason]
properties:
reason:
type: string
enum:
- false_positive
- duplicate
- no_longer_applicable
note:
type: string
maxLength: 255
nullable: true
ReopenFindingRequest:
type: object
required: [reason]
properties:
reason:
$ref: '#/components/schemas/ReopenReasonKey'
note:
type: string
maxLength: 255
nullable: true
SystemClearFindingRequest:
type: object
required: [reason, observed_at]
properties:
reason:
type: string
enum:
- no_longer_drifting
- permission_granted
- permission_removed_from_registry
- role_assignment_removed
- ga_count_within_threshold
observed_at:
type: string
format: date-time
operation_run_id:
type: integer
nullable: true
SystemReopenFindingRequest:
type: object
required: [reason, observed_at]
properties:
reason:
type: string
enum:
- recurred_after_resolution
- verification_failed
observed_at:
type: string
format: date-time
operation_run_id:
type: integer
nullable: true
FindingSummary:
type: object
required:
- id
- tenant_id
- status
- severity
- terminal_outcome
properties:
id:
type: integer
tenant_id:
type: integer
status:
$ref: '#/components/schemas/FindingStatus'
severity:
type: string
resolved_reason:
oneOf:
- $ref: '#/components/schemas/ResolveReasonKey'
- type: 'null'
closed_reason:
oneOf:
- $ref: '#/components/schemas/CloseReasonKey'
- type: 'null'
terminal_outcome:
$ref: '#/components/schemas/TerminalOutcome'
FindingDetail:
allOf:
- $ref: '#/components/schemas/FindingSummary'
- type: object
properties:
resolved_at:
type: string
format: date-time
nullable: true
closed_at:
type: string
format: date-time
nullable: true
reopened_at:
type: string
format: date-time
nullable: true
audit_context:
type: object
additionalProperties: true
description: Logical placeholder for the readable audit/history payload.