openapi: 3.1.0
info:
  title: Assignment Hygiene Surface Contract
  version: 1.0.0
  summary: Logical internal contract for Spec 225 report rendering, overview discoverability, and finding drilldown.
  description: |
    This contract documents the structured payloads and UI-facing surfaces that Spec 225 must satisfy.
    It is intentionally logical rather than public-API only: the feature reuses existing Filament pages,
    workspace overview builders, and finding detail routes instead of introducing a new public controller namespace.
servers:
  - url: https://logical.internal
    description: Non-routable placeholder used to describe internal repository contracts.
paths:
  /internal/findings/hygiene-issues:
    get:
      summary: Evaluate derived findings hygiene issues for the current visible workspace scope.
      description: |
        Logical internal contract implemented by the narrow findings hygiene service.
        It derives `broken_assignment` and `stale_in_progress` from current finding, audit,
        tenant-entitlement, and user lifecycle truth without persisting a second workflow state.
      operationId: listFindingHygieneIssues
      x-not-public-http: true
      parameters:
        - name: workspaceId
          in: query
          required: true
          schema:
            type: integer
        - name: tenantId
          in: query
          required: false
          schema:
            type:
              - integer
              - 'null'
        - name: reason
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/HygieneReasonFilter'
      responses:
        '200':
          description: Derived findings hygiene issues and visible-scope summary counts.
          content:
            application/vnd.tenantpilot.findings-hygiene-report+json:
              schema:
                $ref: '#/components/schemas/FindingsHygieneReportSurface'
  /admin/findings/hygiene:
    get:
      summary: Canonical workspace-context findings hygiene report.
      operationId: viewFindingsHygieneReport
      parameters:
        - name: tenant
          in: query
          required: false
          schema:
            type:
              - string
              - 'null'
        - name: reason
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/HygieneReasonFilter'
      responses:
        '200':
          description: Existing Filament page renders the read-first findings hygiene report.
          content:
            text/html:
              schema:
                type: string
            application/vnd.tenantpilot.findings-hygiene-report+json:
              schema:
                $ref: '#/components/schemas/FindingsHygieneReportSurface'
        '404':
          description: Non-member or out-of-scope workspace remains deny-as-not-found; hidden tenants inside an entitled workspace are suppressed from rows, counts, filters, and hints rather than causing a route-level `404`.
  /admin:
    get:
      summary: Workspace overview includes one findings hygiene signal.
      operationId: viewWorkspaceOverviewWithFindingsHygieneSignal
      responses:
        '200':
          description: Existing workspace overview renders the hygiene summary signal in attention or calm state for the current visible scope.
          content:
            text/html:
              schema:
                type: string
            application/vnd.tenantpilot.findings-hygiene-overview-signal+json:
              schema:
                $ref: '#/components/schemas/FindingsHygieneOverviewSignal'
  /admin/t/{tenant}/findings/{finding}:
    get:
      summary: Report drilldown opens the existing tenant finding detail route.
      operationId: openFindingFromHygieneReport
      parameters:
        - name: tenant
          in: path
          required: true
          schema:
            type: integer
        - name: finding
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: Existing finding detail renders for an entitled tenant operator.
          content:
            text/html:
              schema:
                type: string
        '403':
          description: In-scope member lacks the current capability to inspect the finding.
        '404':
          description: Recipient no longer has tenant or record visibility.
components:
  schemas:
    HygieneReason:
      type: string
      enum:
        - broken_assignment
        - stale_in_progress
    HygieneReasonFilter:
      type: string
      enum:
        - all
        - broken_assignment
        - stale_in_progress
    FindingsHygieneIssue:
      type: object
      required:
        - findingId
        - tenantId
        - tenantName
        - findingSummary
        - status
        - lastWorkflowActivityAt
        - reasons
        - detailUrl
      properties:
        findingId:
          type: integer
        workspaceId:
          type: integer
        tenantId:
          type: integer
        tenantName:
          type: string
        findingSummary:
          type: string
        status:
          type: string
        ownerUserId:
          type:
            - integer
            - 'null'
        ownerName:
          type:
            - string
            - 'null'
        assigneeUserId:
          type:
            - integer
            - 'null'
        assigneeName:
          type:
            - string
            - 'null'
        dueAt:
          type:
            - string
            - 'null'
          format: date-time
        overdue:
          type: boolean
        lastWorkflowActivityAt:
          type:
            - string
            - 'null'
          format: date-time
        reasons:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/HygieneReason'
        detailUrl:
          type: string
    FindingsHygieneSummary:
      type: object
      required:
        - uniqueIssueCount
        - brokenAssignmentCount
        - staleInProgressCount
        - isCalm
      properties:
        uniqueIssueCount:
          type: integer
          minimum: 0
        brokenAssignmentCount:
          type: integer
          minimum: 0
        staleInProgressCount:
          type: integer
          minimum: 0
        isCalm:
          type: boolean
    FindingsHygieneReportSurface:
      type: object
      required:
        - appliedReasonFilter
        - appliedTenantFilter
        - issues
        - summary
      properties:
        appliedReasonFilter:
          $ref: '#/components/schemas/HygieneReasonFilter'
        appliedTenantFilter:
          type:
            - string
            - 'null'
        issues:
          type: array
          items:
            $ref: '#/components/schemas/FindingsHygieneIssue'
        summary:
          $ref: '#/components/schemas/FindingsHygieneSummary'
        emptyState:
          type:
            - object
            - 'null'
          properties:
            title:
              type: string
            description:
              type: string
            ctaLabel:
              type:
                - string
                - 'null'
            ctaUrl:
              type:
                - string
                - 'null'
    FindingsHygieneOverviewSignal:
      type: object
      required:
        - headline
        - description
        - uniqueIssueCount
        - brokenAssignmentCount
        - staleInProgressCount
        - isCalm
        - ctaLabel
        - ctaUrl
      properties:
        headline:
          type: string
        description:
          type: string
          description: Short operator-facing summary derived from broken-assignment and stale-in-progress counts.
        uniqueIssueCount:
          type: integer
          minimum: 0
        brokenAssignmentCount:
          type: integer
          minimum: 0
        staleInProgressCount:
          type: integer
          minimum: 0
        isCalm:
          type: boolean
        ctaLabel:
          type: string
        ctaUrl:
          type: string