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