# Issues_CreateIssue

`POST /v1/accounts/{account}/datasets/{dataset}/issues`

Raises an issue or writes a note, in a person's own words, on an episode
or the whole dataset. A blocking issue raised on an episode a person
ruled clears that ruling in this scope (withdrawing the issue restores
it).

- Base URL: `https://junction-api.qualiastudios.dev`
- Authentication: an API key in the `X-API-Key` header

## Parameters

- `account` (path, string, required): The account id.
- `dataset` (path, string, required): The dataset id.
- `requestId` (query, string): Idempotency key (AIP-155): a retry with the same id within 24 hours
  returns the first call's result instead of creating a second one.

## Request body

Required: `application/json` `qualia.v1.Issue`

## Responses

- `200` OK: `application/json` `qualia.v1.Issue`
- `default` Default error response: `application/json` `google.rpc.Status`

## Schemas

```yaml
qualia.v1.Issue:
  type: object
  properties:
    name:
      type: string
      description: accounts/{account}/datasets/{dataset}/issues/{issue}
    episode:
      type: string
      description: |-
        accounts/{account}/datasets/{dataset}/episodes/{episode}; empty when the
        issue is about the whole dataset.
    code:
      readOnly: true
      type: string
      description: |-
        Machine-readable code (idle_tail, camera_desync, out_of_reach, mix_twin,
        source_starved, ...); empty for a person's own words.
    kind:
      readOnly: true
      enum:
        - KIND_UNSPECIFIED
        - TAKE
        - RECORDING
        - SCENE
        - MOTION
        - GRIPPER
        - CONTROL
        - DISTRIBUTION
        - MIXTURE
      type: string
      description: What it is about.
      format: enum
    title:
      readOnly: true
      type: string
      description: "Short label for the chip: the check's name, or a person's first line."
    text:
      type: string
      description: One human sentence. A person may raise an issue with no words ("raised").
    body:
      type: string
      description: |-
        More than the sentence says: a model's whole explanation, the rest of
        what a person wrote.
    value:
      readOnly: true
      type: string
      description: The figure beside the title, e.g. "4.2 s", "160 ms", "0.4x".
    timeSeconds:
      type: number
      description: A moment in the episode, seconds from the start of the recording.
      format: double
    author:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Actor"
      description: Who raised it.
    blocks:
      type: boolean
      description: |-
        True: an issue that blocks a keep. False: a note. A person converts
        either way with UpdateIssue (emits ISSUE_CONVERTED); a rule's, model's or
        operator's issue can be demoted to a note and promoted back.
    state:
      readOnly: true
      enum:
        - STATE_UNSPECIFIED
        - OPEN
        - DISMISSED
      type: string
      description: Open or dismissed. Changed only by DismissIssues and ReopenIssues.
      format: enum
    dismissal:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Stamp"
      description: Who dismissed it, when; unset while open.
    dismissCause:
      readOnly: true
      enum:
        - DISMISS_CAUSE_UNSPECIFIED
        - MANUAL
        - KEEP
        - TRIM
        - RESOLVED
      type: string
      description: What dismissed it.
      format: enum
    dismissReason:
      readOnly: true
      type: string
      description: Words given with the dismissal.
    conversion:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Stamp"
      description: Who last converted it between issue and note; unset if never.
    replyCount:
      readOnly: true
      type: integer
      description: Replies under it.
      format: int32
    measurement:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Issue_Measurement"
      description: What a rule measured, when it measured something.
    recommended:
      readOnly: true
      type: boolean
      description: |-
        True when the check recommends rejecting the episode (the curation
        suggestion fell under the frozen threshold).
    source:
      readOnly: true
      type: string
      description: |-
        For a mixture-wide issue about one source (starved, apart, read at head,
        read twice): the source it names.
    createTime:
      readOnly: true
      type: string
      description: When it was raised.
      format: date-time
    updateTime:
      readOnly: true
      type: string
      description: When it last changed.
      format: date-time
    etag:
      type: string
      description: Sent back on UpdateIssue.
    check:
      readOnly: true
      type: string
      description: |-
        accounts/{account}/checks/{check}: the check that raised it; empty for
        anything else (a person, the operator, ingest, curation).
    finding:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Issue_Finding"
      description: |-
        The model's reading that last raised it: set on the VLM check's issues
        and notes.
  description: |-
    Anything said about an episode or a whole dataset. An issue
    blocks a keep until it is dismissed or the episode is ruled; a note
    (blocks = false) never blocks. Checks, models, the operator and people all
    raise into the same list.

    The parent dataset is the issue's scope. An issue raised through an upload
    is the episode's own and reads the same from every mixture that contains
    it; an issue raised through a mixture (by its checks or by a person working
    in it) is open in that mixture alone. A merged dataset
    has no issues of its own on its episodes: an issue raised on one through it
    is stored in the upload that holds the take, a dataset-level note is the
    merged dataset's. Every issue stored in an upload a merge took (raised
    before or after the merge) is named, with its episode and its replies,
    under the merged dataset, and is written by that name; its name
    under the upload still reads, and a write by it is FAILED_PRECONDITION
    naming the merged dataset.

    Served by qds-api.
qualia.v1.Actor:
  type: object
  properties:
    kind:
      readOnly: true
      enum:
        - KIND_UNSPECIFIED
        - PERSON
        - VALIDATION
        - RULE
        - MODEL
        - OPERATOR
        - SYSTEM
      type: string
      description: What kind of actor it is.
      format: enum
    user:
      readOnly: true
      type: string
      description: The person's user id when `kind` is PERSON; empty otherwise.
    displayName:
      readOnly: true
      type: string
      description: The person's display name at the time, for rendering without a lookup.
    agent:
      readOnly: true
      type: string
      description: |-
        The rule code, or for a MODEL the Qualia model tier (e.g. "qualia-vlm";
        "qualia" for any other model), when not a person.
    run:
      readOnly: true
      type: string
      description: The run a model or rule finding came from, when it has one.
    apiKey:
      readOnly: true
      type: string
      description: |-
        The API key's id when a person acted through one; empty for a session.
        Never the key itself.
    attribution:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Attribution"
      description: |-
        Signed identity evidence recorded with the change, kept apart from the
        principal above. Unset on older records and wherever none was signed.
  description: Who did something. Every decision, issue and reply names one.
qualia.v1.Attribution:
  type: object
  properties:
    authentication:
      readOnly: true
      enum:
        - AUTHENTICATION_UNSPECIFIED
        - SESSION
        - API_KEY
        - SERVICE
      type: string
      description: How the principal authenticated.
      format: enum
    initiator:
      readOnly: true
      type: string
      description: |-
        The user id of a person verified as starting the work: the person of a
        session, or the person a trusted service's signed job record names.
        Empty when nobody was verified.
    initiatorDisplayName:
      readOnly: true
      type: string
      description: The initiator's display name at the time, never their email.
    credentialCreator:
      readOnly: true
      type: string
      description: The user id of the key's creator at the time. Not who used the key.
    credentialOwner:
      readOnly: true
      enum:
        - CREDENTIAL_OWNER_UNSPECIFIED
        - PERSON
        - SPACE
      type: string
      description: Who owned the key at the time.
      format: enum
    credentialOwnerSpace:
      readOnly: true
      type: string
      description: |-
        The owning space's id (a workspace or the organization) when
        `credential_owner` is SPACE.
  description: |-
    How a change was authenticated and who verifiably started it.
    Only the trusted mesh signs it. It never authorizes anything, and a key's
    creator or owner is never the person who used the key.
qualia.v1.Stamp:
  type: object
  properties:
    actor:
      readOnly: true
      allOf:
        - $ref: "#/components/schemas/qualia.v1.Actor"
      description: Who.
    stampTime:
      readOnly: true
      type: string
      description: When.
      format: date-time
  description: "A signature: who and when. Used for dismissals, conversions and decisions."
qualia.v1.Issue_Measurement:
  type: object
  properties:
    metric:
      readOnly: true
      type: string
      description: The metric, as an Episode.Metrics field name.
    value:
      readOnly: true
      type: number
      description: The episode's value.
      format: double
    fence:
      readOnly: true
      type: number
      description: The fence it crossed.
      format: double
    comparator:
      readOnly: true
      type: string
      description: '">" or "<".'
    population:
      readOnly: true
      type: string
      description: 'Whose fence: "robot", "dataset", "mixture", "version".'
    populationSize:
      readOnly: true
      type: string
      description: Episodes in that population.
    referenceRange:
      readOnly: true
      type: string
      description: |-
        The reference range the episode was judged against, as currently
        computed (each run on the interface refreshes it; the values at
        judgment are `fence` and `population_size` above):
        accounts/{account}/checks/episode-rules/referenceRanges/{reference_range}.
        Set on the episode rules' findings whose fence came from a stored
        range; empty for an absolute limit, an upload judged against itself
        (its ranges are not stored), the curation engine's thresholds, and a
        range a later run no longer computes (it is deleted).
    observed:
      readOnly: true
      type: number
      description: |-
        Ingest: what the data said, in `unit` (a gap's length, a camera's
        rate, an episode's span).
      format: double
    expected:
      readOnly: true
      type: number
      description: |-
        Ingest: what it should have said, in `unit`: the limit it broke or the
        declared value.
      format: double
    delta:
      readOnly: true
      type: number
      description: "Ingest: `observed` minus `expected`, in `unit`."
      format: double
    unit:
      readOnly: true
      type: string
      description: |-
        Ingest: the unit of `observed`, `expected` and `delta`: "s", "rows",
        "hz", "px", ...; empty when unknown.
    count:
      readOnly: true
      type: string
      description: 'Ingest: how many times it happened (the 3 of "3 of 120").'
    total:
      readOnly: true
      type: string
      description: |-
        Ingest: what `count` is out of (the 120 of "3 of 120": samples, rows,
        episodes), when there is such a whole.
    timeSeconds:
      readOnly: true
      type: number
      description: "Ingest: when it started, in seconds from the episode start."
      format: double
    durationSeconds:
      readOnly: true
      type: number
      description: |-
        Ingest: how long it lasted from `time_seconds`, in seconds: a gap, a
        frozen reading, a low-rate window, the episode's whole span. Unset on
        a condition at one moment.
      format: double
    frameIndex:
      readOnly: true
      type: string
      description: "Ingest: the row it was seen at, by position in the episode."
    camera:
      readOnly: true
      type: string
      description: "Ingest: the camera it is about, by its key; empty when none."
    topic:
      readOnly: true
      type: string
      description: "Ingest: the topic or column it is about; empty when none."
  description: |-
    What a rule measured, so the UI can say "jerk_mean 0.61 > 0.42 (Q3+3 IQR
    of this robot's takes)". An ingest finding fills the fields
    from `observed` on instead: what it saw, where and for how
    long, so a reader never parses `text`.
qualia.v1.Issue_Finding:
  type: object
  properties:
    verdict:
      readOnly: true
      enum:
        - VERDICT_UNSPECIFIED
        - PASS
        - FAIL
        - UNCERTAIN
      type: string
      description: What the model concluded.
      format: enum
    confidence:
      readOnly: true
      type: number
      description: How sure it was, 0 to 1.
      format: double
    explanation:
      readOnly: true
      type: string
      description: The evidence, in a few sentences.
    moments:
      readOnly: true
      type: array
      items:
        $ref: "#/components/schemas/qualia.v1.Finding_Moment"
      description: The moments it pointed at, in time order.
    model:
      readOnly: true
      type: string
      description: The Qualia model tier that read it, e.g. "qualia-vlm-fast".
    task:
      readOnly: true
      type: string
      description: "accounts/{account}/tasks/{task}: the task it read the episode against."
    taskRevision:
      readOnly: true
      type: integer
      description: That task's published revision, as frozen into the run.
      format: int32
    checkRun:
      readOnly: true
      type: string
      description: "accounts/{account}/checks/vlm/runs/{run}: the check run that read it."
    readTime:
      readOnly: true
      type: string
      description: When it was read.
      format: date-time
  description: |-
    What a model read in the episode, as the VLM check recorded it. The
    issue's text is its summary.
qualia.v1.Finding_Moment:
  type: object
  properties:
    timeSeconds:
      readOnly: true
      type: number
      description: |-
        Seconds from the start of the recording, as Issue.time_seconds (the
        trim the model read is added in), so a player seeks to it as is.
      format: double
    label:
      readOnly: true
      type: string
      description: What happens there, in a few words; may be empty.
  description: A moment the model pointed at.
google.rpc.Status:
  oneOf:
    - $ref: "#/components/schemas/ApiError"
    - $ref: "#/components/schemas/GatewayError"
  description: "The body of a failed call: one of the two shapes below."
ApiError:
  title: API error
  type: object
  required:
    - code
    - message
    - retryable
    - details
  properties:
    code:
      type: string
      description: The gRPC code name in upper case, e.g. `NOT_FOUND`.
    message:
      type: string
    retryable:
      type: boolean
      description: Whether the same call may succeed if retried.
    details:
      type: array
      items:
        type: object
      description: "Empty, except when a dataset copy or move is refused: then one `google.rpc.ErrorInfo` with the refusal's `reason` code, its `domain` and, in `metadata`, the datasets it names."
  description: A refusal from the API itself.
GatewayError:
  title: Gateway error
  type: object
  required:
    - detail
  properties:
    detail:
      type: string
      description: What went wrong.
  description: A refusal from the gateway before the call reaches the API, for example `401` bad or missing API key, `403` key lacks the scope or environment closed, `413` body too large.
```
