openapi: 3.1.0
info:
  title: Baseline Compare Strategy Extraction Internal Contract
  version: 0.1.0
  summary: Internal logical contract for compare strategy selection, compare launch validation, and strategy-owned subject results
  description: |
    This contract is an internal planning artifact for Spec 203. The affected
    compare surfaces still render through Filament and Livewire, and compare
    execution continues to run through the existing Laravel services and jobs.
    The paths below are logical boundary identifiers for existing service, job,
    and surface entry points only; they do not imply new HTTP controllers or
    routes.
x-logical-artifact: true
x-baseline-compare-strategy-consumers:
  - surface: baseline.compare.start
    sourceFiles:
      - apps/platform/app/Services/Baselines/BaselineCompareService.php
    mustConsume:
      - canonical_scope_v2
      - deterministic_strategy_selection
      - unsupported_or_mixed_scope_rejection
      - strategy_key_recorded_in_run_context
  - surface: baseline.compare.fanout
    sourceFiles:
      - apps/platform/app/Services/Baselines/BaselineCompareService.php
      - apps/platform/app/Filament/Pages/BaselineCompareMatrix.php
      - apps/platform/app/Filament/Resources/BaselineProfileResource/Pages/ViewBaselineProfile.php
    mustConsume:
      - visible_set_strategy_validation
      - single_strategy_per_run_rule
      - tenant_owned_compare_runs_only
  - surface: baseline.compare.execution
    sourceFiles:
      - apps/platform/app/Jobs/CompareBaselineToTenantJob.php
      - apps/platform/app/Services/Baselines/CurrentStateHashResolver.php
      - apps/platform/app/Services/Drift/DriftHasher.php
    mustConsume:
      - compare_orchestration_context
      - compare_subject_result
      - strategy_provided_subject_projection
  - surface: baseline.compare.review
    sourceFiles:
      - apps/platform/app/Filament/Pages/BaselineCompareLanding.php
      - apps/platform/app/Support/Baselines/BaselineCompareSummaryAssessor.php
      - apps/platform/app/Support/Baselines/BaselineCompareExplanationRegistry.php
    mustRender:
      - unsupported_scope_truth
      - incomplete_or_ambiguous_truth
      - failed_strategy_truth
paths:
  /internal/tenants/{tenant}/baseline-profiles/{profile}/compare/validate:
    post:
      summary: Resolve one compatible compare strategy family before compare is enqueued
      operationId: validateBaselineCompareStrategySelection
      parameters:
        - name: tenant
          in: path
          required: true
          schema:
            type: integer
        - name: profile
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompareValidationRequest'
      responses:
        '200':
          description: One compatible strategy family was selected for the requested compare scope
          content:
            application/vnd.tenantpilot.baseline-compare-strategy-selection+json:
              schema:
                $ref: '#/components/schemas/CompareStrategySelection'
        '422':
          description: Scope is unsupported or requires more than one strategy family
          content:
            application/vnd.tenantpilot.baseline-compare-strategy-errors+json:
              schema:
                $ref: '#/components/schemas/CompareStrategySelection'
        '403':
          description: Actor is in scope but lacks capability to start compare
        '404':
          description: Tenant or baseline profile is outside actor scope
  /internal/tenants/{tenant}/baseline-profiles/{profile}/compare:
    post:
      summary: Start baseline compare using one selected strategy family
      operationId: startBaselineCompareWithStrategySelection
      parameters:
        - name: tenant
          in: path
          required: true
          schema:
            type: integer
        - name: profile
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompareValidationRequest'
      responses:
        '202':
          description: Compare accepted with a deterministic strategy family recorded in the existing run context
          content:
            application/vnd.tenantpilot.baseline-compare-run+json:
              schema:
                $ref: '#/components/schemas/CompareLaunchEnvelope'
        '422':
          description: Scope is unsupported or mixed and compare was not started
        '403':
          description: Actor is in scope but lacks capability to start compare
        '404':
          description: Tenant or baseline profile is outside actor scope
  /internal/workspaces/{workspace}/baseline-profiles/{profile}/compare-visible-assignments:
    post:
      summary: Start compare for the visible assigned tenant set only when one strategy family supports the shared scope
      operationId: startVisibleAssignmentCompareWithStrategySelection
      parameters:
        - name: workspace
          in: path
          required: true
          schema:
            type: integer
        - name: profile
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VisibleAssignmentCompareRequest'
      responses:
        '202':
          description: Visible tenant compare fan-out accepted with the same selected strategy family for each tenant-owned run
          content:
            application/vnd.tenantpilot.baseline-compare-fanout+json:
              schema:
                $ref: '#/components/schemas/VisibleAssignmentCompareEnvelope'
        '422':
          description: Visible set scope is unsupported or mixed and no tenant compare runs were started
        '403':
          description: Actor is in scope but lacks workspace baseline manage capability
        '404':
          description: Workspace or baseline profile is outside actor scope
  /internal/baseline-compare/subjects/classify:
    post:
      summary: Logical strategy-owned boundary for classifying one compare subject inside the compare job
      operationId: classifyCompareSubject
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CompareSubjectInput'
      responses:
        '200':
          description: Structured compare-subject result consumable by platform orchestration
          content:
            application/vnd.tenantpilot.compare-subject-result+json:
              schema:
                $ref: '#/components/schemas/CompareSubjectResult'
components:
  schemas:
    CompareStrategyKey:
      type: string
      examples:
        - intune_policy
        - entra_configuration
    StrategySelectionState:
      type: string
      enum:
        - supported
        - unsupported
        - mixed
    ScopeEntryReference:
      type: object
      additionalProperties: false
      required:
        - domain_key
        - subject_class
        - subject_type_keys
      properties:
        domain_key:
          type: string
        subject_class:
          type: string
        subject_type_keys:
          type: array
          items:
            type: string
    CompareStrategyCapability:
      type: object
      additionalProperties: false
      required:
        - strategy_key
        - domain_keys
        - subject_classes
        - compare_supported
        - active
      properties:
        strategy_key:
          $ref: '#/components/schemas/CompareStrategyKey'
        domain_keys:
          type: array
          items:
            type: string
        subject_classes:
          type: array
          items:
            type: string
        subject_type_keys:
          oneOf:
            - type: string
              enum:
                - all
            - type: array
              items:
                type: string
        compare_supported:
          type: boolean
        active:
          type: boolean
    CompareStrategySelection:
      type: object
      additionalProperties: false
      required:
        - selection_state
        - matched_scope_entries
        - rejected_scope_entries
        - operator_reason
        - diagnostics
      properties:
        selection_state:
          $ref: '#/components/schemas/StrategySelectionState'
        strategy_key:
          oneOf:
            - $ref: '#/components/schemas/CompareStrategyKey'
            - type: 'null'
        matched_scope_entries:
          type: array
          items:
            $ref: '#/components/schemas/ScopeEntryReference'
        rejected_scope_entries:
          type: array
          items:
            $ref: '#/components/schemas/ScopeEntryReference'
        operator_reason:
          type: string
        diagnostics:
          type: object
          additionalProperties: true
    CompareValidationRequest:
      type: object
      additionalProperties: false
      required:
        - baseline_profile_id
        - normalized_scope
      properties:
        baseline_profile_id:
          type: integer
        baseline_snapshot_id:
          type:
            - integer
            - 'null'
        normalized_scope:
          type: object
          additionalProperties: true
    CompareLaunchEnvelope:
      type: object
      additionalProperties: false
      required:
        - operation_run_id
        - strategy_selection
      properties:
        operation_run_id:
          type: integer
        strategy_selection:
          $ref: '#/components/schemas/CompareStrategySelection'
    VisibleAssignmentCompareRequest:
      type: object
      additionalProperties: false
      required:
        - baseline_profile_id
        - normalized_scope
        - visible_tenant_ids
      properties:
        baseline_profile_id:
          type: integer
        normalized_scope:
          type: object
          additionalProperties: true
        visible_tenant_ids:
          type: array
          items:
            type: integer
    VisibleAssignmentCompareEnvelope:
      type: object
      additionalProperties: false
      required:
        - strategy_selection
        - started_runs
        - skipped_tenants
      properties:
        strategy_selection:
          $ref: '#/components/schemas/CompareStrategySelection'
        started_runs:
          type: array
          items:
            type: integer
        skipped_tenants:
          type: array
          items:
            type: integer
    CompareSubjectInput:
      type: object
      additionalProperties: false
      required:
        - strategy_key
        - subject_identity
        - baseline_state
        - current_state
      properties:
        strategy_key:
          $ref: '#/components/schemas/CompareStrategyKey'
        subject_identity:
          type: object
          additionalProperties: true
        baseline_state:
          type: object
          additionalProperties: true
        current_state:
          oneOf:
            - type: object
              additionalProperties: true
            - type: 'null'
    CompareSubjectState:
      type: string
      enum:
        - no_drift
        - drift
        - unsupported
        - incomplete
        - ambiguous
        - failed
    CompareSubjectProjection:
      type: object
      additionalProperties: false
      required:
        - platform_subject_class
        - domain_key
        - subject_type_key
        - operator_label
      properties:
        platform_subject_class:
          type: string
        domain_key:
          type: string
        subject_type_key:
          type: string
        operator_label:
          type: string
        summary_kind:
          type:
            - string
            - 'null'
        additional_labels:
          type: object
          additionalProperties:
            type: string
    CompareFindingCandidate:
      type: object
      additionalProperties: false
      required:
        - change_type
        - severity
        - fingerprint_basis
        - evidence_payload
        - auto_close_eligible
      properties:
        change_type:
          type: string
        severity:
          type: string
        fingerprint_basis:
          type: object
          additionalProperties: true
        evidence_payload:
          type: object
          additionalProperties: true
        auto_close_eligible:
          type: boolean
    CompareSubjectResult:
      type: object
      additionalProperties: false
      required:
        - subject_identity
        - projection
        - baseline_availability
        - current_state_availability
        - compare_state
        - trust_level
        - evidence_quality
        - diagnostics
      properties:
        subject_identity:
          type: object
          additionalProperties: true
        projection:
          $ref: '#/components/schemas/CompareSubjectProjection'
        baseline_availability:
          type: string
        current_state_availability:
          type: string
        compare_state:
          $ref: '#/components/schemas/CompareSubjectState'
        trust_level:
          type: string
        evidence_quality:
          type: string
        severity_recommendation:
          type:
            - string
            - 'null'
        finding_candidate:
          oneOf:
            - $ref: '#/components/schemas/CompareFindingCandidate'
            - type: 'null'
        diagnostics:
          type: object
          additionalProperties: true