openapi: 3.1.0
info:
  title: CoreCortex API
  version: 0.1.0-alpha.1
  description: >
    Initial contract for configuring corpora, applying immutable desired state,
    ingesting records, retrieving evidence, and inspecting durable jobs.
  license:
    name: License not yet selected
    identifier: LicenseRef-Unspecified
servers:
  - url: https://api.corecortex.io
    description: Planned hosted endpoint; self-hosted deployments replace this URL

security:
  - bearerAuth: []

tags:
  - name: Configuration
    description: Validate and apply immutable authored state
  - name: Corpora
    description: Inspect corpus and active-index state
  - name: Records
    description: Manage original enterprise records and lifecycle propagation
  - name: Ingestion
    description: Validate and apply immutable prepared record batches
  - name: Retrieval
    description: Retrieve ranked evidence without mandatory synthesis
  - name: Jobs
    description: Inspect durable asynchronous work

paths:
  /healthz:
    get:
      security: []
      operationId: health
      summary: Report process health
      responses:
        '200':
          description: Healthy process

  /v1/configurations:validate:
    post:
      tags: [Configuration]
      security: []
      operationId: validateConfiguration
      summary: Validate authored corpus state without applying it
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CorpusConfig'
      responses:
        '200':
          description: Validation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationResult'
        '400':
          $ref: '#/components/responses/BadRequest'

  /v1/corpora:
    post:
      tags: [Corpora]
      operationId: applyCorpus
      summary: Create or apply a new immutable corpus configuration revision
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CorpusConfig'
      responses:
        '201':
          description: Applied corpus revision
          headers:
            Location:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Corpus'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'

  /v1/corpora/{corpusId}:
    parameters:
      - $ref: '#/components/parameters/TenantId'
      - $ref: '#/components/parameters/CorpusId'
    get:
      tags: [Corpora]
      operationId: getCorpus
      summary: Read corpus state and its active index versions
      responses:
        '200':
          description: Corpus state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Corpus'
        '404':
          $ref: '#/components/responses/NotFound'

  /v1/corpora/{corpusId}/datasets/{datasetId}/ingestion-batches:validate:
    parameters:
      - $ref: '#/components/parameters/TenantId'
      - $ref: '#/components/parameters/CorpusId'
      - $ref: '#/components/parameters/DatasetId'
    post:
      tags: [Ingestion]
      operationId: validateIngestionBatch
      summary: Validate identity, provenance, access, and integrity without applying records
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PreparedIngestionBatch'
      responses:
        '200':
          description: Validation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'

  /v1/corpora/{corpusId}/datasets/{datasetId}/ingestion-batches:
    parameters:
      - $ref: '#/components/parameters/TenantId'
      - $ref: '#/components/parameters/CorpusId'
      - $ref: '#/components/parameters/DatasetId'
    post:
      tags: [Ingestion]
      operationId: applyIngestionBatch
      summary: Apply a validated immutable batch and start lifecycle propagation
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PreparedIngestionBatch'
      responses:
        '202':
          description: Durable ingestion job
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'

  /v1/corpora/{corpusId}/records:delete:
    parameters:
      - $ref: '#/components/parameters/TenantId'
      - $ref: '#/components/parameters/CorpusId'
    post:
      tags: [Records]
      operationId: deleteRecords
      summary: Tombstone records and propagate deletion to derived data
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [recordIds]
              properties:
                recordIds:
                  type: array
                  minItems: 1
                  maxItems: 1000
                  items:
                    type: string
                    format: uuid
      responses:
        '202':
          description: Durable deletion job
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
        '400':
          $ref: '#/components/responses/BadRequest'

  /v1/corpora/{corpusId}/query:
    parameters:
      - $ref: '#/components/parameters/TenantId'
      - $ref: '#/components/parameters/CorpusId'
    post:
      tags: [Retrieval]
      operationId: queryCorpus
      summary: Retrieve ranked evidence and optionally emit a synthesis-hook request
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QueryRequest'
      responses:
        '200':
          description: Ranked evidence and optional explain plan
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QueryResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'

  /v1/jobs/{jobId}:
    get:
      tags: [Jobs]
      operationId: getJob
      summary: Inspect durable job state
      parameters:
        - $ref: '#/components/parameters/TenantId'
        - name: jobId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Job state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
        '404':
          $ref: '#/components/responses/NotFound'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  parameters:
    TenantId:
      name: X-CoreC-Tenant-Id
      in: header
      required: true
      description: Tenant established by the trusted authentication boundary
      schema:
        type: string
        format: uuid
    CorpusId:
      name: corpusId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    DatasetId:
      name: datasetId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: Stable caller-generated key scoped to the authenticated tenant
      schema:
        type: string
        minLength: 16
        maxLength: 200

  responses:
    BadRequest:
      description: Invalid syntax, configuration, or query
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Conflict:
      description: Idempotency or immutable-state conflict
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    NotFound:
      description: Resource not found in the authenticated tenant
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'

  schemas:
    CorpusConfig:
      $ref: '../../schemas/corec-corpus-v1alpha1.schema.json'

    Slug:
      type: string
      pattern: '^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$'

    ValidationResult:
      type: object
      required: [valid, violations]
      properties:
        valid:
          type: boolean
        violations:
          type: array
          items:
            $ref: '#/components/schemas/Violation'

    Violation:
      type: object
      required: [path, message]
      properties:
        path:
          type: string
        message:
          type: string

    Corpus:
      type: object
      required: [id, tenantId, name, revision, createdAt]
      properties:
        id:
          type: string
          format: uuid
        tenantId:
          type: string
          format: uuid
        name:
          $ref: '#/components/schemas/Slug'
        revision:
          type: integer
          format: int64
          minimum: 1
        activeIndexVersions:
          type: object
          additionalProperties:
            type: string
            format: uuid
        createdAt:
          type: string
          format: date-time

    NormalizedRecord:
      type: object
      additionalProperties: false
      required: [record_id, record_type, title, content, attributes, access, source]
      properties:
        record_id:
          type: string
          minLength: 1
        record_type:
          type: string
          minLength: 1
        title:
          type: string
          minLength: 1
        content:
          type: string
          minLength: 1
        attributes:
          type: object
          additionalProperties: true
        access:
          $ref: '#/components/schemas/AccessPolicy'
        source:
          $ref: '#/components/schemas/SourceProvenance'
        provenance:
          $ref: '#/components/schemas/NormalizationProvenance'

    NormalizationProvenance:
      type: object
      additionalProperties: false
      required: [processor, processor_version, source_artifact_sha256]
      properties:
        processor:
          type: string
          minLength: 1
        processor_version:
          type: string
          minLength: 1
        source_artifact_sha256:
          type: string
          pattern: '^[a-f0-9]{64}$'

    SourceProvenance:
      type: object
      additionalProperties: false
      required: [provider, source_id, url, published_at, observed_at, version]
      properties:
        provider:
          type: string
          minLength: 1
        source_id:
          type: string
          minLength: 1
        url:
          type: [string, 'null']
          format: uri
        published_at:
          type: [string, 'null']
        observed_at:
          type: string
          description: ISO date, ISO timestamp, or RFC 2822 acquisition timestamp
        version:
          type: [string, 'null']

    AccessPolicy:
      type: object
      additionalProperties: false
      required: [allow, deny, attributes]
      properties:
        allow:
          type: array
          items:
            type: string
        deny:
          type: array
          items:
            type: string
        attributes:
          type: object
          additionalProperties:
            type: string

    SourceArtifact:
      type: object
      additionalProperties: false
      required: [format, sha256, rows]
      properties:
        format:
          type: string
          enum: [jsonl, parquet]
        sha256:
          type: string
          pattern: '^[a-f0-9]{64}$'
        rows:
          type: integer
          format: int64
          minimum: 1

    PreparedIngestionBatch:
      type: object
      additionalProperties: false
      required:
        - apiVersion
        - batchId
        - dataset
        - configurationRevision
        - mappingFingerprint
        - source
        - part
        - recordsSha256
        - records
      properties:
        apiVersion:
          const: corecortex.io/ingestion/v1alpha1
        batchId:
          type: string
          minLength: 1
        dataset:
          type: string
          minLength: 1
        configurationRevision:
          type: integer
          minimum: 1
        mappingFingerprint:
          type: string
          pattern: '^[a-f0-9]{64}$'
        source:
          $ref: '#/components/schemas/SourceArtifact'
        part:
          type: object
          additionalProperties: false
          required: [number, total]
          properties:
            number:
              type: integer
              minimum: 1
            total:
              type: integer
              minimum: 1
        recordsSha256:
          type: string
          pattern: '^[a-f0-9]{64}$'
        records:
          type: array
          minItems: 1
          maxItems: 1000
          items:
            $ref: '#/components/schemas/NormalizedRecord'

    QueryRequest:
      type: object
      additionalProperties: false
      required: [query, retrievalPlan]
      properties:
        query:
          type: string
          minLength: 1
        retrievalPlan:
          type: string
          minLength: 1
        intent:
          type: [string, 'null']
          enum: [exact, lookup, semantic, comparison, relationship, null]
          description: Non-authoritative routing hint; it never weakens authorization
        filters:
          type: object
          additionalProperties: true
        limit:
          type: [integer, 'null']
          minimum: 1
          maximum: 1000
        explain:
          type: boolean
          default: false
        responsePlan:
          type: [string, 'null']
          minLength: 1
          description: >
            Optional query-time response plan. It never changes retrieval or
            authorization; it asks the host to emit a model-neutral synthesis
            hook envelope for an application-owned worker.

    QueryResponse:
      type: object
      required:
        - corpusId
        - retrievalPlan
        - evidence
        - requestId
        - traceId
      properties:
        corpusId:
          type: string
          format: uuid
        retrievalPlan:
          type: string
        evidence:
          type: array
          items:
            $ref: '#/components/schemas/Evidence'
        synthesisHook:
          oneOf:
            - $ref: '#/components/schemas/SynthesisHookRequest'
            - type: 'null'
          description: >
            Authorized evidence envelope for a configured external synthesis
            worker. Absent when no response plan was requested.
        explain:
          type: [object, 'null']
          additionalProperties: true
        requestId:
          type: string
        traceId:
          type: string

    Evidence:
      type: object
      required:
        - recordId
        - fragmentId
        - datasetId
        - sourceId
        - title
        - score
        - rank
        - content
        - contentSha256
        - processor
        - processorVersion
      properties:
        recordId:
          type: string
          format: uuid
        fragmentId:
          type: string
          format: uuid
        datasetId:
          type: string
          format: uuid
        sourceId:
          type: string
        title:
          type: string
        score:
          type: number
        rank:
          type: integer
          minimum: 1
        content:
          type: string
        attributes:
          type: object
          additionalProperties: true
        sourceVersion:
          type: [string, 'null']
        contentSha256:
          type: string
          pattern: '^[a-f0-9]{64}$'
        processor:
          type: string
        processorVersion:
          type: string

    SynthesisHookRequest:
      type: object
      additionalProperties: false
      required: [query, responsePlan, requireCitations, evidence, traceId]
      properties:
        query:
          type: string
          minLength: 1
        responsePlan:
          type: string
          minLength: 1
        requireCitations:
          type: boolean
        evidence:
          type: array
          items:
            $ref: '#/components/schemas/SynthesisEvidence'
        traceId:
          type: string
          minLength: 1

    SynthesisEvidence:
      type: object
      additionalProperties: false
      required: [fragmentId, recordId, content, source, attributes]
      properties:
        fragmentId:
          type: string
          minLength: 1
        recordId:
          type: string
          minLength: 1
        content:
          type: string
          minLength: 1
        source:
          type: object
          additionalProperties: true
        attributes:
          type: object
          additionalProperties: true

    SynthesisHookResponse:
      type: object
      additionalProperties: false
      required: [answer, abstained, citations, model, usage]
      properties:
        answer:
          type: string
          minLength: 1
        abstained:
          type: boolean
        citations:
          type: array
          items:
            $ref: '#/components/schemas/SynthesisCitation'
        model:
          type: string
          minLength: 1
        usage:
          type: object
          additionalProperties: true

    SynthesisCitation:
      type: object
      additionalProperties: false
      required: [fragmentId]
      properties:
        fragmentId:
          type: string
          minLength: 1

    Job:
      type: object
      required: [id, type, state, createdAt]
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
        state:
          type: string
          enum: [pending, running, succeeded, failed, cancelled]
        createdAt:
          type: string
          format: date-time
        startedAt:
          type: [string, 'null']
          format: date-time
        finishedAt:
          type: [string, 'null']
          format: date-time
        progress:
          type: object
          additionalProperties: true

    Problem:
      type: object
      required: [type, title, status, code, requestId, traceId]
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
          minimum: 400
          maximum: 599
        code:
          type: string
        detail:
          type: string
        violations:
          type: array
          items:
            $ref: '#/components/schemas/Violation'
        requestId:
          type: string
        traceId:
          type: string
