# Proposed contract, version 1: endpoints, errors, paging, idempotency, security and the capability of every
# operation. Resource schemas were initialized from the data model (docs/06-data) and are kept aligned with it; input
# schemas (*Input, *Request) and views are specific to this contract. Status: proposal for review, not confirmed design.
# Conventions: docs/05-interfaces/choosing-an-interface.md. Vendor extensions: x-moonitoring-audience (who calls),
# x-moonitoring-capability (the capability checked), x-moonitoring-entitlement (the entitlement feature checked),
# x-moonitoring-requirements (the requirements of 1.requisiti-raffinati.txt the operation serves).
openapi: 3.1.0
info:
  title: Farm Files API
  version: 1.0.0
  description: Raw files as received, what was skipped when parsing them and the parse jobs that processed them.
    Organization users see what their sources sent and what failed, upload files of their own feeds and withdraw them;
    staff see everything, retry failed jobs and ask for re-parses.
servers:
- url: https://farm.moonitoring.com/files/v1
  description: Prod
- url: https://stage-farm.moonitoring.com/files/v1
  description: Stage, through IAP
security:
- bearer: []
tags:
- name: Raw files
  description: Files in the raw bucket
- name: Uploads
  description: Files an Organization sends by hand
- name: Skips
  description: Values not published canonically
- name: Parse jobs
  description: Processing of raw files
paths:
  /organizations/{organization_id}/raw-files:
    get:
      summary: List a farm's raw files
      operationId: listOrganizationRawFiles
      tags:
      - Raw files
      parameters:
      - name: organization_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The Organization.
      - name: feed
        in: query
        required: false
        schema:
          type: string
          format: uuid
        description: Only this feed.
      - name: from
        in: query
        required: false
        schema:
          type: string
          format: date-time
        description: Received at or after.
      - name: to
        in: query
        required: false
        schema:
          type: string
          format: date-time
        description: Received before.
      - name: data_hash
        in: query
        required: false
        schema:
          type: string
        description: Exactly this content hash.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/RawFileView'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      x-moonitoring-requirements:
      - '30'
      - '1002.17'
      - '1006.20'
  /raw-files/{raw_file_id}:
    get:
      summary: Read a raw file's metadata
      operationId: getRawFile
      tags:
      - Raw files
      parameters:
      - name: raw_file_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RawFileView'
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      x-moonitoring-requirements:
      - '30'
  /raw-files/{raw_file_id}/download-url:
    post:
      summary: Get a download link
      operationId: getRawFileDownloadUrl
      description: 'A short-lived signed URL to the compressed file in the raw bucket. Logical withdrawal does not
        remove the original or bypass this operation''s authorization checks. A physically erased file (deleted_at
        present) returns 410 without a URL; an unexpectedly unavailable object returns 409 without a URL. Every
        download is in the Data Access log.'
      tags:
      - Raw files
      parameters:
      - name: raw_file_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: ../../../../common/v1/schemas.yaml#/components/schemas/SignedUrl
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '410':
          description: The raw object was physically erased; no download URL is issued.
          content:
            application/problem+json:
              schema:
                $ref: ../../../../common/v1/schemas.yaml#/components/schemas/Problem
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      x-moonitoring-requirements:
      - '30.3'
      - '1002.17'
      - '1006.20'
  /raw-files/{raw_file_id}/remove:
    post:
      summary: Remove an uploaded file
      operationId: removeRawFile
      description: 'Logically withdraws a file of the Organization''s own FileChannel or MoonitoringAppChannel feeds.
        In one WriteCommit of kind file_removal, records the withdrawal and cancels the active evidence derived
        from this file. Repeating the withdrawal returns the existing result without another cancellation revision.
        Pending or retried parsing must not republish withdrawn evidence. The original object and its metadata
        remain available under the raw-retention policy; physical deletion occurs only through erasure.
        withdrawn_at records this logical action; deleted_at is not set by this endpoint.'
      tags:
      - Raw files
      parameters:
      - name: raw_file_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RawFileView'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
      x-moonitoring-audience: organization
      x-moonitoring-capability: farm.animal_records.edit_own
      x-moonitoring-requirements:
      - '30.7'
      - '1002.20'
      - '61.3'
  /raw-files/{raw_file_id}/skips:
    get:
      summary: List a raw file's skips
      operationId: listRawFileSkips
      tags:
      - Skips
      parameters:
      - name: raw_file_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/IngestionSkip'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      x-moonitoring-requirements:
      - '41'
  /raw-files/{raw_file_id}/parse-jobs:
    get:
      summary: List a raw file's parse jobs
      operationId: listRawFileParseJobs
      tags:
      - Parse jobs
      parameters:
      - name: raw_file_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/ParseJob'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      x-moonitoring-requirements:
      - '51'
  /organizations/{organization_id}/uploads:
    post:
      summary: Announce an upload
      operationId: createUpload
      description: 'Gate 1: a matching hash avoids upload only when the server verified the stored object against
        its uncompressed hash and size and it is still available. A matching metadata entry with a missing or
        erased object returns 409, never known or a replacement upload target; recovery is outside this handshake.
        A logically withdrawn
        file returns 409; announcing the same bytes does not restore its evidence. Otherwise a resumable signed
        URL to the raw bucket is returned; the API never carries the bytes.'
      tags:
      - Uploads
      parameters:
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/IdempotencyKey
      - name: organization_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The Organization.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UploadRequest'
      responses:
        '201':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadTarget'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: organization
      x-moonitoring-capability: farm.animal_records.edit_own
      x-moonitoring-requirements:
      - '30'
      - 32.*.4
      - 32.*.5
      - '1002.20'
  /uploads/{upload_id}/commit:
    post:
      summary: Commit an upload
      operationId: commitUpload
      tags:
      - Uploads
      parameters:
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/IdempotencyKey
      - name: upload_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UploadCommitRequest'
      responses:
        '201':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RawFileView'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: organization
      x-moonitoring-capability: farm.animal_records.edit_own
      x-moonitoring-requirements:
      - '30'
      - '51'
      - '52'
      - '1002.20'
  /organizations/{organization_id}/skips:
    get:
      summary: Summarize a farm's skips
      operationId: listOrganizationSkips
      tags:
      - Skips
      parameters:
      - name: organization_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
        description: The Organization.
      - name: feed
        in: query
        required: false
        schema:
          type: string
          format: uuid
        description: Only this feed.
      - name: from
        in: query
        required: false
        schema:
          type: string
          format: date-time
        description: Parsed at or after.
      - name: to
        in: query
        required: false
        schema:
          type: string
          format: date-time
        description: Parsed before.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/SkipSummary'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      x-moonitoring-requirements:
      - '41'
      - '1002.17'
      - '1006.20'
  /skips/{skip_id}/detail-url:
    post:
      summary: Get the row-level detail of a skip
      operationId: getSkipDetailUrl
      description: 'A signed URL to the JSONL file next to the raw file: record and field locator, source record
        key, time and raw value of every skipped row.'
      tags:
      - Skips
      parameters:
      - name: skip_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: ../../../../common/v1/schemas.yaml#/components/schemas/SignedUrl
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      x-moonitoring-requirements:
      - '41.7'
  /parse-jobs:
    get:
      summary: List parse jobs
      operationId: listParseJobs
      description: An Organization sees the jobs of its own feeds; staff see every job. Failed jobs and the dead-letter
        queue are read from here.
      tags:
      - Parse jobs
      parameters:
      - name: status
        in: query
        required: false
        schema:
          type: string
          enum:
          - pending
          - running
          - done
          - failed
        description: Only this status.
      - name: failure_class
        in: query
        required: false
        schema:
          type: string
          enum:
          - timeout
          - lease_expired
          - dependency_unavailable
          - invalid_file
          - unsupported_version
          - parser_error
        description: Only this failure class.
      - name: feed
        in: query
        required: false
        schema:
          type: string
          format: uuid
        description: Only this feed.
      - name: sync_run
        in: query
        required: false
        schema:
          type: string
          format: uuid
        description: Only jobs of this run's raw inputs.
      - name: trigger
        in: query
        required: false
        schema:
          type: string
          enum:
          - upload
          - parser_change
          - manual
        description: Only this trigger.
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Cursor
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/Limit
      responses:
        '200':
          description: One page of results.
          content:
            application/json:
              schema:
                type: object
                description: One page of items.
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/ParseJob'
                  next_cursor:
                    type: string
                    description: Where the next page starts; absent on the last page.
                required:
                - items
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      x-moonitoring-requirements:
      - '51'
      - '1006.20'
    post:
      summary: Ask for a re-parse
      operationId: createParseJobs
      description: 'Gate 2: a raw file with a pending or running job for the same parser version is not queued twice.
        Every call is a PlatformAuditEvent.'
      tags:
      - Parse jobs
      parameters:
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/IdempotencyKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReparseRequest'
      responses:
        '201':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ParseJobsCreated'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: staff
      x-moonitoring-capability: farm.parse_jobs.manage
      x-moonitoring-requirements:
      - '51.9'
      - '51.10'
      - '51.11'
      - '51.12'
      - '30'
  /parse-jobs/{job_id}:
    get:
      summary: Read a parse job
      operationId: getParseJob
      tags:
      - Parse jobs
      parameters:
      - name: job_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ParseJob'
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
      x-moonitoring-audience: any
      x-moonitoring-capability:
      - farm.ingestion.read
      - farm.ingestion.read_all
      x-moonitoring-requirements:
      - '51'
  /parse-jobs/{job_id}/retry:
    post:
      summary: Retry a failed job
      operationId: retryParseJob
      description: A new job with previous_job set, on the same queue through the same gates.
      tags:
      - Parse jobs
      parameters:
      - $ref: ../../../../common/v1/schemas.yaml#/components/parameters/IdempotencyKey
      - name: job_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
              required:
              - reason
      responses:
        '201':
          description: The result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ParseJob'
        '400':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/BadRequest
        '401':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Unauthorized
        '403':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Forbidden
        '404':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/NotFound
        '409':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/Conflict
        '422':
          $ref: ../../../../common/v1/schemas.yaml#/components/responses/UnprocessableEntity
      x-moonitoring-audience: staff
      x-moonitoring-capability: farm.parse_jobs.manage
      x-moonitoring-requirements:
      - '51.12'
      - '51.13'
components:
  schemas:
    RawFileView:
      description: API view of RawMetadata with logical withdrawal state. This does not change the persistence
        meaning of RawMetadata.deleted_at, which records physical deletion only.
      allOf:
      - $ref: '#/components/schemas/RawMetadata'
      - type: object
        properties:
          withdrawn_at:
            type: string
            format: date-time
            description: When the file's evidence was logically withdrawn. Absent for an active file; the raw
              object is retained until erasure even when this field is present.
    RawMetadata:
      description: 'Points to one original file, kept in file storage exactly as we received it, with a fingerprint
        (hash) and size to check that it is intact. Values in raw files never enter canonical data directly: parsers
        read the file again whenever a mapping changes.'
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Key.
        source_producer:
          type: string
          format: uuid
          description: ID of the SourceProducerEntity. The producer of the raw data.
        data_path:
          type: string
          description: Path to the raw compressed data file in the storage system.
        data_hash:
          type: string
          description: Hash of the uncompressed content, so the same data compressed differently is still recognised.
        hash_type:
          type: string
          enum:
          - xxh3
          - sha256
          - md5
        data_size:
          type: integer
          description: Size of the raw uncompressed data file in bytes.
        deleted_at:
          type: string
          format: date-time
          description: When the raw data file was deleted from the storage system; empty when still present.
        source_feed:
          type: string
          format: uuid
          description: ID of the SourceFeed. The feed of `source_producer` (`SourceProducerEntity.source_feed`),
            repeated here to make a file unique per feed.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - source_producer
      - data_path
      - data_hash
      - hash_type
      - data_size
      - source_feed
      - created_at
      - updated_at
    IngestionSkip:
      description: Counts the source values that were not published as canonical data, per parse job, field and
        reason, with a row-by-row log next to the raw file. A missing observation can mean "not publishable" rather
        than "not measured"; this is where to check.
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Key.
        raw_metadata:
          type: string
          format: uuid
          description: ID of the RawMetadata.
        source_field:
          type: string
        reason:
          type: string
          enum:
          - unit_unknown
          - semantic_basis_unknown
          - unmapped_field
          - out_of_range
          - not_measured_marker
          - invalid_format
          - unresolved_subject
          - invalid_identifier
          - out_of_scope
          - unsupported_result_form
          - proprietary_index
        count:
          type: integer
          default: 1
        mapping_version:
          type: string
        detail_path:
          type: string
          description: 'Row-level log in the bucket next to the raw file (JSONL: record/field locator, `source_record_key`,
            time, animal only if safely resolved, raw value). Summary counts alone are not row-level evidence; this
            file is.'
        parse_job:
          type: string
          format: uuid
          description: ID of the ParseJob.
        created_at:
          type: string
          format: date-time
      required:
      - id
      - raw_metadata
      - source_field
      - reason
      - count
      - mapping_version
      - detail_path
      - parse_job
      - created_at
    ParseJob:
      description: 'The work queue of the parsers, the programs that read raw files: one job per raw file and parser
        version. It makes receiving the same file twice harmless and records how raw data became canonical data.
        A failed job carries a failure class that decides whether it is retried or waits for a new parser; it never
        changes the status of the SyncRun that delivered the file.'
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Key.
        raw_metadata:
          type: string
          format: uuid
          description: ID of the RawMetadata.
        parser_version:
          type: string
        status:
          type: string
          enum:
          - pending
          - running
          - done
          - failed
        attempts:
          type: integer
          default: 0
        next_attempt_at:
          type: string
          format: date-time
        lease_until:
          type: string
          format: date-time
          description: A running job whose lease expired is taken again by the scheduler.
        last_error:
          type: string
        trigger:
          type: string
          enum:
          - upload
          - parser_change
          - manual
          description: '`upload`: first parse of a new raw file. `parser_change`: a new parser or mapping version.
            `manual`: asked by a Moonitoring user, e.g. to retry a failed job or to re-parse after an animal identity
            was resolved.'
        requested_by:
          type: string
          format: uuid
          description: ID of the MoonitoringUser. Who asked; set only when trigger = manual.
        reason:
          type: string
          description: Why, in one line a person can act on, e.g. "unit of field DIM confirmed"; set only when trigger
            = manual.
        previous_job:
          type: string
          format: uuid
          description: ID of the ParseJob. The job this one runs again, for a retry or a re-parse of the same raw
            file.
        failure_class:
          type: string
          enum:
          - timeout
          - lease_expired
          - dependency_unavailable
          - invalid_file
          - unsupported_version
          - parser_error
          description: 'Set only when status = failed. Transient classes (timeout, `lease_expired`, `dependency_unavailable`)
            are retried with backoff until attempts reach the limit, then the message goes to the dead-letter topic.
            Permanent classes (`invalid_file`, `unsupported_version`, `parser_error`) are not retried: the job fails
            at once and waits for a new parser version (trigger `parser_change`) or a manual job. `last_error` says
            why.'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - id
      - raw_metadata
      - parser_version
      - status
      - attempts
      - trigger
      - created_at
      - updated_at
    UploadRequest:
      type: object
      description: Announces a file an Organization uploads by hand (a lab report, a spreadsheet) through one of
        its FileChannel feeds, or a payload typed in the app through its MoonitoringAppChannel feed.
      properties:
        feed:
          type: string
          format: uuid
          description: ID of the SourceFeed; must be a FileChannel or MoonitoringAppChannel feed of the Organization.
        hash_type:
          type: string
          enum:
          - xxh3
          - sha256
          - md5
        data_hash:
          type: string
          description: Hash of the uncompressed content.
        data_size:
          type: integer
          description: Uncompressed bytes.
        file_name:
          type: string
        content_type:
          type: string
        coverage:
          $ref: ../../../../common/v1/schemas.yaml#/components/schemas/Period
      required:
      - feed
      - hash_type
      - data_hash
      - data_size
    UploadTarget:
      type: object
      description: 'Where to put the bytes. known means the feed has a verified, available object for this content:
        nothing to upload, and the existing raw file is returned. A hash match alone is insufficient. This is a
        storage-reuse result, not proof that the current delivery was processed. Withdrawn files are rejected
        before returning this response; known never restores withdrawn evidence.'
      properties:
        upload_id:
          type: string
          format: uuid
        known:
          type: boolean
        raw_file:
          $ref: '#/components/schemas/RawFileView'
        upload:
          $ref: ../../../../common/v1/schemas.yaml#/components/schemas/SignedUrl
      required:
      - upload_id
      - known
      allOf:
      - if:
          properties:
            known:
              const: true
          required:
          - known
        then:
          required:
          - raw_file
          not:
            required:
            - upload
          properties:
            raw_file:
              not:
                anyOf:
                - required:
                  - deleted_at
                - required:
                  - withdrawn_at
        else:
          required:
          - upload
          not:
            required:
            - raw_file
    UploadCommitRequest:
      type: object
      description: 'Confirms the upload finished and the server verified the stored object against its announced
        uncompressed hash and size: one SyncRun of kind full_snapshot for the file''s coverage is recorded, with
        its RawMetadata and ParseJob, in one transaction. A withdrawn file returns 409; a commit does not restore
        withdrawn evidence. Declaring full_snapshot alone never authorizes inferred deletions: source completeness,
        the complete keys manifest, matching scope and authoritative source order must also be established.'
      properties:
        source_producer:
          type: object
          description: Who produced the file's content; the channel is the feed's.
          properties:
            origin_version:
              type: string
              format: uuid
            channel_version:
              type: string
              format: uuid
            produced_at:
              type: string
              format: date-time
          required:
          - origin_version
    SkipSummary:
      type: object
      description: Skips of a feed or Organization, grouped by reason and source field.
      properties:
        reason:
          type: string
          enum:
          - unit_unknown
          - semantic_basis_unknown
          - unmapped_field
          - out_of_range
          - not_measured_marker
          - invalid_format
          - unresolved_subject
          - invalid_identifier
          - out_of_scope
          - unsupported_result_form
          - proprietary_index
        source_field:
          type: string
        count:
          type: integer
        raw_files:
          type: integer
          description: How many raw files had this skip.
        last_seen_at:
          type: string
          format: date-time
      required:
      - reason
      - source_field
      - count
    ReparseRequest:
      type: object
      description: Asks to parse again one raw file, every raw input of a SyncRun, or a feed over a period. Each
        raw file gets its own job with trigger manual; records whose key and content are unchanged produce no new
        revision.
      properties:
        raw_file:
          type: string
          format: uuid
        sync_run:
          type: string
          format: uuid
        feed:
          type: string
          format: uuid
        period:
          $ref: ../../../../common/v1/schemas.yaml#/components/schemas/Period
        parser_version:
          type: string
          description: 'Default: the current version.'
        reason:
          type: string
          description: One line a person can act on, e.g. unit of field DIM confirmed.
      required:
      - reason
    ParseJobsCreated:
      type: object
      description: The jobs that were queued.
      properties:
        jobs:
          type: array
          items:
            $ref: '#/components/schemas/ParseJob'
        skipped_active:
          type: integer
          description: Raw files that already had a pending or running job for this parser version.
      required:
      - jobs
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'The short-lived token signed by `access`: an Organization user token, a staff token or a client
        token, as the operation''s audience says.'
