openapi: 3.1.0
info:
  title: MoDaCor Runtime Service API
  version: 0.1.0-draft
  description: |
    Draft OpenAPI contract for long-lived MoDaCor pipeline sessions.
    This schema accompanies the runtime service API reference in server-api.md.
servers:
  - url: /v1
tags:
  - name: Service
  - name: Sessions
  - name: Sources
  - name: Sinks
  - name: Processing
  - name: Runs
  - name: Chunked Outputs

paths:
  /health:
    get:
      tags: [Service]
      summary: Liveness probe for the runtime process
      responses:
        "200":
          description: Process is alive
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    const: ok

  /readiness:
    get:
      tags: [Service]
      summary: Readiness probe with runtime metrics
      responses:
        "200":
          description: Service readiness and session metrics
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReadinessResponse"

  /source-templates:
    get:
      tags: [Sources]
      summary: List built-in source templates/profiles
      responses:
        "200":
          description: Templates map
          content:
            application/json:
              schema:
                type: object
                properties:
                  templates:
                    type: object
                    additionalProperties:
                      type: object

  /chunked-outputs:
    post:
      tags: [Chunked Outputs]
      summary: Initialize a server-managed chunked output
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InitializeChunkedOutputRequest"
      responses:
        "201":
          description: Output initialized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChunkedOutputStatus"

  /chunked-outputs/reopen:
    post:
      tags: [Chunked Outputs]
      summary: Reconstruct an output handle from a persistent manifest
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReopenChunkedOutputRequest"
      responses:
        "201":
          description: Output handle reconstructed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChunkedOutputStatus"

  /chunked-outputs/{output_id}:
    parameters:
      - $ref: "#/components/parameters/OutputId"
    get:
      tags: [Chunked Outputs]
      summary: Inspect the authoritative chunk manifest
      parameters:
        - name: offset
          in: query
          schema: {type: integer, minimum: 0, default: 0}
        - name: limit
          in: query
          schema: {type: integer, minimum: 1, default: 100}
      responses:
        "200":
          description: Current output status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChunkedOutputStatus"
    delete:
      tags: [Chunked Outputs]
      summary: Detach the process-local handle without deleting backend data
      responses:
        "204":
          description: Handle detached

  /chunked-outputs/{output_id}/recover:
    parameters:
      - $ref: "#/components/parameters/OutputId"
    post:
      tags: [Chunked Outputs]
      summary: Reconcile, abandon, or resume an incomplete assembly
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RecoverChunkedOutputRequest"
      responses:
        "200":
          description: Recovery action applied
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChunkedOutputStatus"
        "409":
          description: Recovery action is invalid for the current state

  /chunked-outputs/{output_id}/finalize:
    parameters:
      - $ref: "#/components/parameters/OutputId"
    post:
      tags: [Chunked Outputs]
      summary: Validate and publish a completed chunked output
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [plan_hash]
              properties:
                plan_hash: {type: string}
      responses:
        "200":
          description: Output finalized, or already complete
        "409":
          description: Plan mismatch or incomplete output

  /sessions:
    get:
      tags: [Sessions]
      summary: List all sessions
      responses:
        "200":
          description: Session summaries
          content:
            application/json:
              schema:
                type: object
                properties:
                  sessions:
                    type: array
                    items:
                      $ref: "#/components/schemas/SessionSummary"
    post:
      tags: [Sessions]
      summary: Create a new session
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSessionRequest"
      responses:
        "201":
          description: Session created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionDetail"
        "409":
          description: Session id already exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}:
    parameters:
      - $ref: "#/components/parameters/SessionId"
    get:
      tags: [Sessions]
      summary: Get session details
      responses:
        "200":
          description: Session details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionDetail"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/errors/latest:
    parameters:
      - $ref: "#/components/parameters/SessionId"
    get:
      tags: [Sessions]
      summary: Get latest error diagnostics for a session
      responses:
        "200":
          description: Current and latest recorded error diagnostics
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorDiagnosticsResponse"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags: [Sessions]
      summary: Delete a session
      responses:
        "204":
          description: Session deleted
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/sources:
    parameters:
      - $ref: "#/components/parameters/SessionId"
    put:
      tags: [Sources]
      summary: Upsert one or more source registrations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpsertSourcesRequest"
      responses:
        "200":
          description: Sources updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  session_id:
                    type: string
                  sources:
                    type: array
                    items:
                      $ref: "#/components/schemas/SourceRegistration"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/sources/{ref}:
    parameters:
      - $ref: "#/components/parameters/SessionId"
      - name: ref
        in: path
        required: true
        schema:
          type: string
    delete:
      tags: [Sources]
      summary: Delete a source registration
      responses:
        "204":
          description: Source removed
        "404":
          description: Session or source not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/sources/patch:
    parameters:
      - $ref: "#/components/parameters/SessionId"
    post:
      tags: [Sources]
      summary: Convenience upsert for a single source
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SourceRegistration"
      responses:
        "200":
          description: Source updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  session_id:
                    type: string
                  source:
                    $ref: "#/components/schemas/SourceRegistration"
                  sources:
                    type: array
                    items:
                      $ref: "#/components/schemas/SourceRegistration"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/sinks:
    parameters:
      - $ref: "#/components/parameters/SessionId"
    put:
      tags: [Sinks]
      summary: Upsert one or more sink registrations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpsertSinksRequest"
      responses:
        "200":
          description: Sinks updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  session_id:
                    type: string
                  sinks:
                    type: array
                    items:
                      $ref: "#/components/schemas/SinkRegistration"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/sinks/{ref}:
    parameters:
      - $ref: "#/components/parameters/SessionId"
      - name: ref
        in: path
        required: true
        schema:
          type: string
    delete:
      tags: [Sinks]
      summary: Delete a sink registration
      responses:
        "204":
          description: Sink removed
        "404":
          description: Session or sink not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/sinks/patch:
    parameters:
      - $ref: "#/components/parameters/SessionId"
    post:
      tags: [Sinks]
      summary: Convenience upsert for a single sink
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SinkRegistration"
      responses:
        "200":
          description: Sink updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  session_id:
                    type: string
                  sink:
                    $ref: "#/components/schemas/SinkRegistration"
                  sinks:
                    type: array
                    items:
                      $ref: "#/components/schemas/SinkRegistration"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/sample:
    parameters:
      - $ref: "#/components/parameters/SessionId"
    post:
      tags: [Sources]
      summary: Shortcut to upsert the `sample` source reference
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [location]
              properties:
                location:
                  type: string
                type:
                  type: string
                  default: hdf
                kwargs:
                  type: object
                  additionalProperties: true
      responses:
        "200":
          description: Sample source updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  session_id:
                    type: string
                  source:
                    $ref: "#/components/schemas/SourceRegistration"
                  sources:
                    type: array
                    items:
                      $ref: "#/components/schemas/SourceRegistration"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/process:
    parameters:
      - $ref: "#/components/parameters/SessionId"
    post:
      tags: [Processing]
      summary: Trigger processing run
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProcessRequest"
      responses:
        "202":
          description: Run accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunAcceptedResponse"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/process/dry-run:
    parameters:
      - $ref: "#/components/parameters/SessionId"
    post:
      tags: [Processing]
      summary: Preview invalidation/selection plan without executing a run
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProcessRequest"
      responses:
        "200":
          description: Dry-run plan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DryRunPlan"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: Session busy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/reset:
    parameters:
      - $ref: "#/components/parameters/SessionId"
    post:
      tags: [Processing]
      summary: Reset session state (without processing)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ResetRequest"
      responses:
        "202":
          description: Reset accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  session_id:
                    type: string
                  mode:
                    type: string
                    enum: [partial, full]
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/recover:
    parameters:
      - $ref: "#/components/parameters/SessionId"
    post:
      tags: [Processing]
      summary: Recover after failed partial/full run
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RecoverRequest"
      responses:
        "202":
          description: Recovery accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunAcceptedResponse"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/runs:
    parameters:
      - $ref: "#/components/parameters/SessionId"
    get:
      tags: [Runs]
      summary: List runs for a session
      responses:
        "200":
          description: Run summaries
          content:
            application/json:
              schema:
                type: object
                properties:
                  session_id:
                    type: string
                  runs:
                    type: array
                    items:
                      $ref: "#/components/schemas/RunSummary"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /sessions/{session_id}/runs/{run_id}:
    parameters:
      - $ref: "#/components/parameters/SessionId"
      - name: run_id
        in: path
        required: true
        schema:
          type: string
    get:
      tags: [Runs]
      summary: Get detailed run metadata
      responses:
        "200":
          description: Run detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RunDetail"
        "404":
          description: Session or run not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

components:
  parameters:
    OutputId:
      name: output_id
      in: path
      required: true
      schema:
        type: string
    SessionId:
      name: session_id
      in: path
      required: true
      schema:
        type: string

  schemas:
    ReadinessMetrics:
      type: object
      properties:
        session_count:
          type: integer
          minimum: 0
        active_run_count:
          type: integer
          minimum: 0
        error_session_count:
          type: integer
          minimum: 0
        error_session_ids:
          type: array
          items:
            type: string
        last_updated_utc:
          type: [string, "null"]
          format: date-time

    ReadinessResponse:
      type: object
      required: [status, ready, metrics]
      properties:
        status:
          type: string
          enum: [ready, degraded]
        ready:
          type: boolean
        metrics:
          $ref: "#/components/schemas/ReadinessMetrics"

    SessionState:
      type: string
      enum: [idle, running_partial, running_full, error_partial, error_full]

    PipelineReference:
      type: object
      properties:
        yaml_text:
          type: string
        yaml_path:
          type: string
      oneOf:
        - required: [yaml_text]
        - required: [yaml_path]

    TraceConfig:
      type: object
      properties:
        enabled:
          type: boolean
          default: false
        watch:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
        record_only_on_change:
          type: boolean
          default: true
        snapshot_processing_data:
          type: boolean
          default: false
          description: Capture full ProcessingData snapshots after traced steps for HDF export.
        snapshot_step_ids:
          type: array
          description: Optional step ids to snapshot. Empty means every executed step when snapshot_processing_data is true.
          items:
            type: string

    CreateSessionRequest:
      type: object
      required: [session_id, pipeline]
      properties:
        session_id:
          type: string
        name:
          type: string
        source_profile:
          type: string
        pipeline:
          $ref: "#/components/schemas/PipelineReference"
        trace:
          $ref: "#/components/schemas/TraceConfig"
        auto_full_reset_on_partial_error:
          type: boolean
          default: true

    SourceRegistration:
      type: object
      required: [ref, type, location]
      properties:
        ref:
          type: string
        type:
          type: string
          enum: [hdf, yaml, csv, tiled, custom]
        location:
          type: string
        kwargs:
          type: object
          additionalProperties: true

    SinkRegistration:
      type: object
      required: [ref, type, location]
      properties:
        ref:
          type: string
        type:
          type: string
          enum: [csv, hdf, hdf_chunked, hdf_processing, custom]
        location:
          type: string
        kwargs:
          type: object
          additionalProperties: true

    InitializeChunkedOutputRequest:
      type: object
      required: [subpath]
      properties:
        sink:
          $ref: "#/components/schemas/SinkRegistration"
        session_id:
          type: string
        sink_ref:
          type: string
        subpath:
          type: string
        collision:
          type: string
          enum: [error, resume, replace]
          default: error
        plan:
          type: object
          description: Complete serialized ChunkPlan, including its declared output layouts.
          additionalProperties: true
        provisional_plan:
          type: object
          description: ProvisionalChunkPlan for source discovery and pilot output-schema resolution.
          additionalProperties: true
      allOf:
        - oneOf:
            - required: [sink]
            - required: [session_id, sink_ref]
        - oneOf:
            - required: [plan]
            - required: [provisional_plan, session_id]

    ReopenChunkedOutputRequest:
      type: object
      required: [plan_id]
      properties:
        sink:
          $ref: "#/components/schemas/SinkRegistration"
        session_id:
          type: string
        sink_ref:
          type: string
        plan_id:
          type: string
        plan_hash:
          type: string
      oneOf:
        - required: [sink]
        - required: [session_id, sink_ref]

    RecoverChunkedOutputRequest:
      type: object
      required: [action]
      properties:
        action:
          type: string
          enum: [reconcile, abandon, resume]

    ChunkedOutputStatus:
      type: object
      required: [output_id, status, plan_id, plan_hash, expected_chunks, completed_chunks]
      properties:
        output_id: {type: string}
        status: {type: string}
        plan_id: {type: string}
        plan_hash: {type: string}
        provisional_hash: {type: string}
        resolution_hash: {type: string}
        pilot_chunk_id: {type: string}
        expected_chunks: {type: integer, minimum: 1}
        completed_chunks: {type: integer, minimum: 0}
        writing_chunks: {type: integer, minimum: 0}
        failed_chunks: {type: integer, minimum: 0}
        missing_chunks: {type: integer, minimum: 0}
        chunks:
          type: array
          items:
            type: object
            properties:
              chunk_id: {type: string}
              ordinal: {type: integer, minimum: 0}
              status: {type: string}

    ChunkPublishRequest:
      type: object
      required: [output_id]
      properties:
        output_id: {type: string}
        chunk_id:
          type: string
          description: Server-managed work id for a provisional or resolved provisional plan.
        chunk_spec:
          type: object
          description: Complete serialized ChunkSpec for this run.
          additionalProperties: true
      oneOf:
        - required: [chunk_id]
        - required: [chunk_spec]

    UpsertSourcesRequest:
      type: object
      required: [sources]
      properties:
        sources:
          type: array
          items:
            $ref: "#/components/schemas/SourceRegistration"

    UpsertSinksRequest:
      type: object
      required: [sinks]
      properties:
        sinks:
          type: array
          items:
            $ref: "#/components/schemas/SinkRegistration"

    WriteHdfRequest:
      type: object
      required: [path]
      properties:
        path:
          type: string
        run_name:
          type: string
        write_all_processing_data:
          type: boolean
          default: false
        data_paths:
          type: array
          items:
            type: string

    ProcessRequest:
      type: object
      required: [mode]
      properties:
        mode:
          type: string
          enum: [partial, full, auto]
        changed_sources:
          type: array
          items:
            type: string
        changed_keys:
          type: array
          items:
            type: string
        run_name:
          type: string
        write_hdf:
          $ref: "#/components/schemas/WriteHdfRequest"
        chunk_output:
          $ref: "#/components/schemas/ChunkPublishRequest"

    ResetRequest:
      type: object
      required: [mode]
      properties:
        mode:
          type: string
          enum: [partial, full]

    RecoverRequest:
      type: object
      required: [strategy]
      properties:
        strategy:
          type: string
          enum: [full_reset_then_process, full_reset_only]
        changed_sources:
          type: array
          items:
            type: string

    SessionSummary:
      type: object
      required: [session_id, state]
      properties:
        session_id:
          type: string
        name:
          type: string
        state:
          $ref: "#/components/schemas/SessionState"
        active_run_id:
          type: string
          nullable: true
        updated_utc:
          type: string
          format: date-time

    SessionDetail:
      allOf:
        - $ref: "#/components/schemas/SessionSummary"
        - type: object
          properties:
            sources:
              type: array
              items:
                $ref: "#/components/schemas/SourceRegistration"
            sinks:
              type: array
              items:
                $ref: "#/components/schemas/SinkRegistration"
            trace:
              $ref: "#/components/schemas/TraceConfig"
            last_run:
              $ref: "#/components/schemas/RunSummary"
            source_profile:
              type: string
              nullable: true
            required_source_refs:
              type: array
              items:
                type: string

    ErrorDiagnosticsResponse:
      type: object
      required: [session_id, state, active_run_id, updated_utc, current_error, latest_error, latest_failed_run]
      properties:
        session_id:
          type: string
        state:
          $ref: "#/components/schemas/SessionState"
        active_run_id:
          type: string
          nullable: true
        updated_utc:
          type: string
          format: date-time
        current_error:
          allOf:
            - $ref: "#/components/schemas/ErrorObject"
          nullable: true
        latest_error:
          allOf:
            - $ref: "#/components/schemas/ErrorObject"
          nullable: true
        latest_failed_run:
          allOf:
            - $ref: "#/components/schemas/RunDetail"
          nullable: true

    RunStatus:
      type: string
      enum: [queued, running, succeeded, failed]

    RunSummary:
      type: object
      required: [run_id, status, mode]
      properties:
        run_id:
          type: string
        mode:
          type: string
          enum: [partial, full, auto]
        status:
          $ref: "#/components/schemas/RunStatus"
        started_utc:
          type: string
          format: date-time
        finished_utc:
          type: string
          format: date-time
          nullable: true
        failed_step_id:
          type: string
          nullable: true
        elapsed_s:
          type: number
          nullable: true
        skipped_steps:
          type: array
          items:
            type: string
        step_durations_s:
          type: object
          additionalProperties:
            type: number

    RunDetail:
      allOf:
        - $ref: "#/components/schemas/RunSummary"
        - type: object
          properties:
            changed_sources:
              type: array
              items:
                type: string
            changed_keys:
              type: array
              items:
                type: string
            dirty_steps:
              type: array
              items:
                type: string
            checkpoint_boundary_step:
              type: string
              nullable: true
            fallback_reason:
              type: string
              nullable: true
            recovered_from_run_id:
              type: string
              nullable: true
            hdf_output:
              type: string
              nullable: true
            error:
              $ref: "#/components/schemas/ErrorObject"

    RunAcceptedResponse:
      type: object
      required: [session_id, run_id, state]
      properties:
        session_id:
          type: string
        run_id:
          type: string
        state:
          $ref: "#/components/schemas/SessionState"

    DryRunPlan:
      type: object
      required: [session_id, mode, effective_mode, dirty_steps, can_process]
      properties:
        session_id:
          type: string
        mode:
          type: string
          enum: [partial, full, auto]
        effective_mode:
          type: string
          enum: [partial, full]
        changed_sources:
          type: array
          items:
            type: string
        changed_keys:
          type: array
          items:
            type: string
        topological_steps:
          type: array
          items:
            type: string
        dirty_steps:
          type: array
          items:
            type: string
        skipped_steps:
          type: array
          items:
            type: string
        checkpoint_boundary_step:
          type: string
          nullable: true
        fallback_strategy:
          type: string
          nullable: true
        missing_required_sources:
          type: array
          items:
            type: string
        warnings:
          type: array
          items:
            type: string
        can_process:
          type: boolean

    ErrorObject:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
        message:
          type: string
        details:
          type: object
          additionalProperties: true
        run_id:
          type: string
        recorded_utc:
          type: string
          format: date-time
        effective_mode:
          type: string
          enum: [partial, full, auto]

    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          $ref: "#/components/schemas/ErrorObject"
