openapi: 3.1.0
info:
  title: Heavy Suite Segmentation Logical Contract
  version: 1.0.0
  summary: Logical contract for classifying heavy UI test families, validating lane placement, and reading heavy-attribution reports.
  description: |
    This is a logical contract for repository tooling, tests, and planning artifacts.
    It does not imply a new runtime HTTP service. It documents the expected
    semantics of heavy-family classification, lane-placement validation, and
    heavy-lane report attribution so the existing lane-governance seams remain
    consistent as Spec 208 is implemented.
x-logical-contract: true
servers:
  - url: https://tenantatlas.local/logical
paths:
  /heavy-test-classifications:
    get:
      summary: Read the checked-in heavy classification catalog.
      operationId: listHeavyTestClassifications
      responses:
        '200':
          description: Current heavy classification catalog.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - classifications
                properties:
                  classifications:
                    type: array
                    minItems: 5
                    items:
                      $ref: '#/components/schemas/HeavyTestClassification'
  /heavy-test-placement-rules:
    get:
      summary: Read the checked-in lane placement rules.
      operationId: listHeavyTestPlacementRules
      responses:
        '200':
          description: Current lane placement rules.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - rules
                properties:
                  rules:
                    type: array
                    minItems: 5
                    items:
                      $ref: '#/components/schemas/LanePlacementRule'
  /heavy-test-families:
    get:
      summary: Read the seeded heavy test family inventory.
      operationId: listHeavyTestFamilies
      responses:
        '200':
          description: Current heavy family inventory.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - families
                properties:
                  families:
                    type: array
                    minItems: 1
                    items:
                      $ref: '#/components/schemas/HeavyTestFamily'
  /heavy-test-families/mixed-file-resolutions:
    get:
      summary: Read the checked-in mixed-file resolution inventory.
      operationId: listMixedFileResolutions
      responses:
        '200':
          description: Current mixed-file resolution records.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - resolutions
                properties:
                  resolutions:
                    type: array
                    items:
                      $ref: '#/components/schemas/MixedFileResolution'
  /heavy-test-families/placement-validations:
    post:
      summary: Validate that a heavy class or family is assigned to a compatible lane.
      operationId: validateHeavyFamilyPlacement
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LanePlacementValidationRequest'
      responses:
        '200':
          description: Placement validation result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LanePlacementValidationResult'
  /test-lanes/{laneId}/reports/heavy-attribution/latest:
    get:
      summary: Read the latest heavy-attribution report for a lane.
      operationId: getLatestHeavyAttributionReport
      parameters:
        - name: laneId
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/LaneId'
      responses:
        '200':
          description: Latest heavy-attribution report for the requested lane.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HeavyAttributionReport'
components:
  schemas:
    ClassificationId:
      type: string
      enum:
        - ui-light
        - ui-workflow
        - surface-guard
        - discovery-heavy
        - browser
    LaneId:
      type: string
      enum:
        - fast-feedback
        - confidence
        - heavy-governance
        - browser
        - profiling
        - junit
    PlacementAllowance:
      type: string
      enum:
        - required
        - allowed
        - discouraged
        - forbidden
    ValidationStatus:
      type: string
      enum:
        - seeded
        - reviewed
        - migrated
        - guarded
    BudgetStatus:
      type: string
      enum:
        - within-budget
        - warning
        - over-budget
    BudgetBaselineSource:
      type: string
      enum:
        - measured-current-suite
        - measured-lane
        - measured-post-spec-207
    BudgetEnforcement:
      type: string
      enum:
        - report-only
        - warn
        - hard-fail
    BudgetLifecycleState:
      type: string
      enum:
        - draft
        - measured
        - documented
        - enforced
    HeavyFamilySelector:
      type: object
      additionalProperties: false
      required:
        - selectorType
        - selectorValue
        - selectorRole
        - sourceOfTruth
      properties:
        selectorType:
          type: string
          enum:
            - suite
            - path
            - group
            - file
        selectorValue:
          type: string
          minLength: 1
        selectorRole:
          type: string
          enum:
            - include
            - exclude
            - inventory-only
        sourceOfTruth:
          type: string
          enum:
            - manifest
            - pest-group
            - guard-test
            - report-attribution
        rationale:
          type: string
    HeavyTestClassification:
      type: object
      additionalProperties: false
      required:
        - classificationId
        - purpose
        - dominantCostDrivers
        - defaultLaneId
        - allowedLaneIds
        - forbiddenLaneIds
        - reviewerSignals
        - escalationTriggers
      properties:
        classificationId:
          $ref: '#/components/schemas/ClassificationId'
        purpose:
          type: string
        dominantCostDrivers:
          type: array
          minItems: 1
          items:
            type: string
        defaultLaneId:
          $ref: '#/components/schemas/LaneId'
        allowedLaneIds:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/LaneId'
        forbiddenLaneIds:
          type: array
          items:
            $ref: '#/components/schemas/LaneId'
        reviewerSignals:
          type: array
          minItems: 1
          items:
            type: string
        escalationTriggers:
          type: array
          minItems: 1
          items:
            type: string
      allOf:
        - if:
            properties:
              classificationId:
                const: browser
          then:
            properties:
              defaultLaneId:
                const: browser
              allowedLaneIds:
                type: array
                minItems: 1
                maxItems: 1
                items:
                  const: browser
        - if:
            properties:
              classificationId:
                const: surface-guard
          then:
            properties:
              defaultLaneId:
                const: heavy-governance
              forbiddenLaneIds:
                allOf:
                  - contains:
                      const: fast-feedback
                  - contains:
                      const: confidence
        - if:
            properties:
              classificationId:
                const: ui-workflow
          then:
            properties:
              defaultLaneId:
                const: confidence
        - if:
            properties:
              classificationId:
                const: discovery-heavy
          then:
            properties:
              defaultLaneId:
                const: heavy-governance
              forbiddenLaneIds:
                allOf:
                  - contains:
                      const: fast-feedback
                  - contains:
                      const: confidence
    HeavyTestFamily:
      type: object
      additionalProperties: false
      required:
        - familyId
        - classificationId
        - purpose
        - currentLaneId
        - targetLaneId
        - selectors
        - hotspotFiles
        - costSignals
        - validationStatus
      properties:
        familyId:
          type: string
        classificationId:
          $ref: '#/components/schemas/ClassificationId'
        purpose:
          type: string
        currentLaneId:
          $ref: '#/components/schemas/LaneId'
        targetLaneId:
          $ref: '#/components/schemas/LaneId'
        selectors:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/HeavyFamilySelector'
        hotspotFiles:
          type: array
          minItems: 1
          items:
            type: string
        costSignals:
          type: array
          minItems: 1
          items:
            type: string
        confidenceRationale:
          type: string
        validationStatus:
          $ref: '#/components/schemas/ValidationStatus'
      allOf:
        - if:
            properties:
              targetLaneId:
                const: confidence
          then:
            required:
              - confidenceRationale
            properties:
              confidenceRationale:
                type: string
                minLength: 1
        - if:
            properties:
              classificationId:
                const: ui-light
          then:
            properties:
              targetLaneId:
                enum:
                  - fast-feedback
                  - confidence
        - if:
            properties:
              classificationId:
                const: ui-workflow
          then:
            properties:
              targetLaneId:
                enum:
                  - confidence
                  - heavy-governance
        - if:
            properties:
              classificationId:
                const: surface-guard
          then:
            properties:
              targetLaneId:
                const: heavy-governance
        - if:
            properties:
              classificationId:
                const: discovery-heavy
          then:
            properties:
              targetLaneId:
                const: heavy-governance
        - if:
            properties:
              classificationId:
                const: browser
          then:
            properties:
              targetLaneId:
                const: browser
    MixedFileResolution:
      type: object
      additionalProperties: false
      required:
        - filePath
        - primaryClassificationId
        - resolutionStrategy
        - rationale
        - followUpRequired
      properties:
        filePath:
          type: string
          minLength: 1
        primaryClassificationId:
          $ref: '#/components/schemas/ClassificationId'
        secondaryClassificationIds:
          type: array
          items:
            $ref: '#/components/schemas/ClassificationId'
        resolutionStrategy:
          type: string
          enum:
            - split-file
            - broadest-cost-wins
        rationale:
          type: string
          minLength: 1
        followUpRequired:
          type: boolean
    LanePlacementRule:
      type: object
      additionalProperties: false
      required:
        - ruleId
        - classificationId
        - laneId
        - allowance
        - reason
      properties:
        ruleId:
          type: string
        classificationId:
          $ref: '#/components/schemas/ClassificationId'
        laneId:
          $ref: '#/components/schemas/LaneId'
        allowance:
          $ref: '#/components/schemas/PlacementAllowance'
        reason:
          type: string
        exceptionPolicy:
          type: string
    LanePlacementValidationRequest:
      type: object
      additionalProperties: false
      required:
        - laneId
      properties:
        laneId:
          $ref: '#/components/schemas/LaneId'
        classificationId:
          $ref: '#/components/schemas/ClassificationId'
        familyId:
          type: string
        filePath:
          type: string
        observedCostSignals:
          type: array
          items:
            type: string
      anyOf:
        - required:
            - classificationId
        - required:
            - familyId
        - required:
            - filePath
    LanePlacementValidationResult:
      type: object
      additionalProperties: false
      required:
        - laneId
        - resolvedClassificationId
        - allowance
        - valid
        - reasons
      properties:
        laneId:
          $ref: '#/components/schemas/LaneId'
        resolvedClassificationId:
          $ref: '#/components/schemas/ClassificationId'
        familyId:
          type: string
        allowance:
          $ref: '#/components/schemas/PlacementAllowance'
        valid:
          type: boolean
        reasons:
          type: array
          minItems: 1
          items:
            type: string
        remediationOptions:
          type: array
          items:
            type: string
        mixedFileResolution:
          $ref: '#/components/schemas/MixedFileResolution'
    HeavyBudgetEvaluation:
      type: object
      additionalProperties: false
      required:
        - targetType
        - targetId
        - thresholdSeconds
        - measuredSeconds
        - baselineSource
        - enforcement
        - lifecycleState
        - status
      properties:
        targetType:
          type: string
          enum:
            - lane
            - classification
            - family
        targetId:
          type: string
        thresholdSeconds:
          type: integer
          minimum: 1
        measuredSeconds:
          type: number
          minimum: 0
        baselineSource:
          $ref: '#/components/schemas/BudgetBaselineSource'
        enforcement:
          $ref: '#/components/schemas/BudgetEnforcement'
        lifecycleState:
          $ref: '#/components/schemas/BudgetLifecycleState'
        status:
          $ref: '#/components/schemas/BudgetStatus'
    HeavyAttributionReport:
      type: object
      additionalProperties: false
      required:
        - laneId
        - finishedAt
        - wallClockSeconds
        - slowestEntries
        - classificationAttribution
        - familyAttribution
        - budgetEvaluations
        - artifactDirectory
      properties:
        laneId:
          $ref: '#/components/schemas/LaneId'
        finishedAt:
          type: string
          format: date-time
        wallClockSeconds:
          type: number
          minimum: 0
        slowestEntries:
          type: array
          description: Canonical top 10 runtime hotspot entries ordered by slowest wall-clock time.
          minItems: 10
          items:
            type: object
            additionalProperties: false
            required:
              - label
              - wallClockSeconds
            properties:
              label:
                type: string
              wallClockSeconds:
                type: number
                minimum: 0
        classificationAttribution:
          type: array
          minItems: 1
          items:
            type: object
            additionalProperties: false
            required:
              - classificationId
              - totalWallClockSeconds
            properties:
              classificationId:
                $ref: '#/components/schemas/ClassificationId'
              totalWallClockSeconds:
                type: number
                minimum: 0
              hotspotFiles:
                type: array
                items:
                  type: string
        familyAttribution:
          type: array
          minItems: 1
          items:
            type: object
            additionalProperties: false
            required:
              - familyId
              - classificationId
              - totalWallClockSeconds
            properties:
              familyId:
                type: string
              classificationId:
                $ref: '#/components/schemas/ClassificationId'
              totalWallClockSeconds:
                type: number
                minimum: 0
              hotspotFiles:
                type: array
                items:
                  type: string
        budgetEvaluations:
          type: array
          minItems: 1
          allOf:
            - contains:
                type: object
                required:
                  - targetType
                  - targetId
                properties:
                  targetType:
                    const: lane
                  targetId:
                    const: heavy-governance
            - contains:
                type: object
                required:
                  - targetType
                properties:
                  targetType:
                    const: classification
            - contains:
                type: object
                required:
                  - targetType
                properties:
                  targetType:
                    const: family
          items:
            $ref: '#/components/schemas/HeavyBudgetEvaluation'
        artifactDirectory:
          type: string
          const: storage/logs/test-lanes
