# List Retests

- **Method:** `GET`
- **Path:** `/api/v1/runs/{run_id}/issues/{issue_id}/retests`
- **Operation ID:** `list_retests`
- **Tags:** artifact

Every retest asked for on one issue, newest first, each with its status and, once it has one, its
result. Empty for an issue nobody has retested.

## Effective servers

- `https://api.saram.sh`

  Saram

## Authentication

- **HTTPBearer**: HTTP bearer

  a Saram API key (`sk_saram_<env>_...`) or a person's AuthKit access token, as `Authorization: Bearer <credential>`. Both are told apart by shape and produce the same principal.

## Path parameters

- **`run_id` (required)**: `string`
- **`issue_id` (required)**: `string`

## Responses

### 200 Successful Response

**Content type:** `application/json`

schema: `RetestList`, no additional properties

Every retest asked for on one issue, newest first.

- **`issue_id` (required)**: `string`
- **`retests` (required)**: `array`

  Newest first.

  **Items:**

  schema: `RetestRecord`, no additional properties

  One retest of one issue: a later replay a person asked for, kept beside what the run concluded.

  The issue itself - `GET /runs/{id}/issues/{issue_id}` - still says what the run's own reproduction found,
  and so does the artifact: a retest never rewrites the run's record. Every retest is its own record.
  - **`created_at` (required)**: `string`, format: `date-time`
  - **`issue_id` (required)**: `string`
  - **`retest_id` (required)**: `string`
  - **`run_id` (required)**: `string`
  - **`status` (required)**: `string`, possible values: `"pending", "running", "completed", "failed"`

    `pending` until the replay has a pool seat (it queues for one like any session), `running` while it
    replays, then `completed` with a `result`, or `failed` with an `error` and no result.
  - **`workflow_id` (required)**: `string`
  - **`attempts`**: `array`

    **Items:**

    schema: `RetestAttempt`, no additional properties

    One replay attempt of a retest: the session it ran in, what it expected at the judged step, and what it
    saw.
    - **`attempt_id` (required)**: `string`
    - **`expected` (required)**: `object`

      The outcome signature the run recorded at the judged step: `url_after` and the failure `markers`.
    - **`issue_id` (required)**: `string`
    - **`outcome` (required)**: `string`

      `reproduced`, `not_reproduced`, `path_diverged`, `target_moved` or `error`.
    - **`session_id` (required)**: `string`

      The throwaway body session the attempt ran in: `<run_id>.<retest_id>_<issue_id>_a<n>`, never one the run's
      own reproduction used, so its evidence sits beside the original's rather than over it.
    - **`notes`**: `string`, default: `""`
    - **`observed`**: `object | null`
    - **`screenshot_refs`**: `array of string`
    - **`surface_trace_ref`**: `string | null`
  - **`check`**: `object | null`, schema: `Check`, no additional properties

    The claim check, present only when a marker matched and it was asked.
    - **`verdict` (required)**: `string`

      Schema: `Verdict1`. Known values, open (more may be added): `supported`, `unsupported`, `ungrounded`, `skipped`.

      `supported`: the checker answered yes with a quote that gates against the ledger text of the cited steps - the only verdict that lets `status` read `verified`. `unsupported`: the checker answered no; the title claims more than the quoted line shows. `ungrounded`: the checker answered yes but its quote is not in the ledger, so the answer is discarded the way an ungrounded diary quote is and recorded in the gate ledger. `skipped`: no marker matched, or the check did not run. A classification: a later verdict is additive.
    - **`checked_by`**: `string | null`

      Schema: `CheckedBy`. Known values, open (more may be added): `judge`, `code`, `manual`.

      Which instrument answered: `judge` for the observer's checker model call under its own instructions, `code` for a mechanical rule, `manual` for a human. A classification.
    - **`quote`**: `string | null`
    - **`quote_step`**: `object | null`, schema: `QuoteStep`, no additional properties

      The step whose ledger block the quote came from.
      - **`session_id` (required)**: `string`
      - **`step_id` (required)**: `integer`
    - **`reason`**: `string | null`
  - **`entry_url`**: `string | null`

    The deployment this replay opened, when it was not the run's own.
  - **`error`**: `string | null`

    Why a `failed` retest has no result, in a sentence for the caller: the target's reset hook failing or
    refused before a replay, quoted with `target: ` in front; that Saram could not read the target's credential;
    or that Saram could not start or finish the retest. Never a finding about the product. Absent on every other
    status.
  - **`finished_at`**: `string | null`, format: `date-time`
  - **`gate_discards`**: `integer | null`

    How many claims this replay's gate threw away: claim-check quotes the cited evidence does not contain, each
    of which leaves `check` `ungrounded`, and attempts whose path diverged before the judged step.
    Zero is the expected value. Absent until `status` is `completed`, and on retests completed before this was
    recorded.
  - **`gate_ref`**: `string | null`

    The records behind `gate_discards`, fetched through `GET /api/v1/blobs`: `{"run_id", "retest_id",
    "issue_id", "discards"}`, one discard per claim or attempt with its `session_id`, `kind`, `reason` and a
    `payload` naming the `issue_id` and the `quote` or the step the path diverged at. This retest's own, never the
    run's reproduction's. Written even when empty, so an absent ref is a retest that has not completed, never
    "nothing thrown away".
  - **`requested_by`**: `string | null`

    The subject of the credential that asked: `key_...`, a person's user id, or `operator`.
  - **`result`**: `string | null`

    Schema: `Result1`. Known values, open (more may be added): `reproduced`, `not_reproduced`, `flaky`, `skipped`.

    What this replay concluded, in `issue.reproduction.result`'s words. `not_reproduced`: the failure the run saw
    at the judged step did not happen again on either attempt - the answer to "is it fixed?" that is yes as far as
    this replay can tell, and not a proof the product works. `reproduced`: it happened again on both. `flaky`: on
    one of two. `skipped`: the replay could not rule (no browser pool, a session it could not open, a control that
    moved). Absent until `status` is `completed`.
  - **`started_at`**: `string | null`, format: `date-time`
  - **`verification`**: `object | null`, schema: `Verification`, no additional properties

    The `claim.verification` block this replay would have written onto the issue: `verified` only when the
    failure happened again and the claim check supported the title.
    - **`status` (required)**: `string`, possible values: `"verified", "marker_matched", "inconclusive", "unverified"`

      Schema: `Status`.

      Best-supported first. `verified`: a replay matched the failure marker at `action_step` AND the claim check answered `supported` with a verbatim quote from the cited steps (`check`) AND the failure is attributed to the product (`attribution`) - or `method` is `assertion` or `manual` and the same two conditions hold. This and only this may file a ticket alone, and `reproducible` is true exactly when this reads `verified`. `marker_matched` (0.5): a replay matched the failure marker at `action_step`; the claim as titled is unchecked, unsupported, or the check could not quote. The step failed again; whether it failed for the stated reason is not established, so it never files alone. `inconclusive`: a check ran and settled nothing (the marker did not appear again, the two attempts disagreed) - not a refutation. `unverified`: nothing checked it, because there was nothing to check, reproduction never ruled, or the finding is a tester error. A wrong total or a select that resets is `unverified` by construction, not disproven. Default and safe reading when the block is absent. This enum drives an action - it is the ticket-filing gate, like `issue.severity` - so it stays closed for readers too and adding a member is a breaking change: a gate must refuse a value it cannot interpret rather than guess at one, which is why `marker_matched` made 0.5 a new family.
    - **`markers`**: `(array of string) | null`
    - **`method`**: `string | null`

      Schema: `Method`. Known values, open (more may be added): `marker`, `assertion`, `manual`.

      Which check ran, when one did; absent when `status` is `unverified`. `marker`: a replay compared the failure marker at `action_step` - name it in `markers`. A marker matching is evidence the step failed again, not that it failed for the reason the title gives, so on its own it produces `marker_matched`, and `verified` only with `check.verdict: supported` and `attribution: product`. `assertion`: a state assertion checked the claim itself (reserved; not produced yet). `manual`: a human checked it. A classification: nothing acts on it - every surface shows `status` - so a reader on an older release buckets an instrument it does not know as `unknown`, and adding one is additive.
    - **`note`**: `string | null`
- **`run_id` (required)**: `string`

**Example:**

```json
{
  "run_id": "",
  "issue_id": "",
  "retests": [
    {
      "retest_id": "",
      "run_id": "",
      "issue_id": "",
      "status": "pending",
      "entry_url": "",
      "requested_by": "",
      "workflow_id": "",
      "result": "reproduced",
      "verification": {
        "status": "verified",
        "method": "marker",
        "markers": [
          ""
        ],
        "note": ""
      },
      "check": {
        "verdict": "supported",
        "quote": "",
        "quote_step": {
          "session_id": "",
          "step_id": 1
        },
        "reason": "",
        "checked_by": "judge"
      },
      "attempts": [
        {
          "attempt_id": "",
          "issue_id": "",
          "session_id": "",
          "outcome": "",
          "expected": {
            "additionalProperty": "anything"
          },
          "observed": {
            "additionalProperty": "anything"
          },
          "notes": "",
          "surface_trace_ref": "",
          "screenshot_refs": [
            ""
          ]
        }
      ],
      "gate_discards": 1,
      "gate_ref": "",
      "error": "",
      "created_at": "",
      "started_at": "",
      "finished_at": ""
    }
  ]
}
```

### 403 the API key lacks the required scope

### 422 Validation Error

**Content type:** `application/json`

schema: `HTTPValidationError`

- **`detail`**: `array`

  **Items:**

  schema: `ValidationError`
  - **`loc` (required)**: `array of (string | integer)`
  - **`msg` (required)**: `string`
  - **`type` (required)**: `string`
  - **`ctx`**: `object`
  - **`input`**: `any`

**Example:**

```json
{
  "detail": [
    {
      "loc": [
        ""
      ],
      "msg": "",
      "type": "",
      "input": null,
      "ctx": {}
    }
  ]
}
```

## Schemas

- `Check` — shown above.

### CheckedBy

**Type:** `string`

Known values, open (more may be added): `judge`, `code`, `manual`.

Which instrument answered: `judge` for the observer's checker model call under its own instructions, `code` for a mechanical rule, `manual` for a human. A classification.

**Example:**

```json
"judge"
```

**Example:**

```json
"code"
```

**Example:**

```json
"manual"
```

- `HTTPValidationError` — shown above.

### Method

**Type:** `string`

Known values, open (more may be added): `marker`, `assertion`, `manual`.

Which check ran, when one did; absent when `status` is `unverified`. `marker`: a replay compared the failure marker at `action_step` - name it in `markers`. A marker matching is evidence the step failed again, not that it failed for the reason the title gives, so on its own it produces `marker_matched`, and `verified` only with `check.verdict: supported` and `attribution: product`. `assertion`: a state assertion checked the claim itself (reserved; not produced yet). `manual`: a human checked it. A classification: nothing acts on it - every surface shows `status` - so a reader on an older release buckets an instrument it does not know as `unknown`, and adding one is additive.

**Example:**

```json
"marker"
```

**Example:**

```json
"assertion"
```

**Example:**

```json
"manual"
```

- `QuoteStep` — shown above.

### Result1

**Type:** `string`

Known values, open (more may be added): `reproduced`, `not_reproduced`, `flaky`, `skipped`.

What the replay showed. `reproduced`: the failure happened again. `not_reproduced`: it did not. `flaky`: the attempts disagreed. `skipped`: no replay ran. More values may be added; read one you do not know as unknown.

**Example:**

```json
"reproduced"
```

**Example:**

```json
"not_reproduced"
```

**Example:**

```json
"flaky"
```

**Example:**

```json
"skipped"
```

- `RetestAttempt` — shown above.
- `RetestList` — shown above.
- `RetestRecord` — shown above.

### Status

**Type:** `string`, possible values: `"verified", "marker_matched", "inconclusive", "unverified"`

Best-supported first. `verified`: a replay matched the failure marker at `action_step` AND the claim check answered `supported` with a verbatim quote from the cited steps (`check`) AND the failure is attributed to the product (`attribution`) - or `method` is `assertion` or `manual` and the same two conditions hold. This and only this may file a ticket alone, and `reproducible` is true exactly when this reads `verified`. `marker_matched` (0.5): a replay matched the failure marker at `action_step`; the claim as titled is unchecked, unsupported, or the check could not quote. The step failed again; whether it failed for the stated reason is not established, so it never files alone. `inconclusive`: a check ran and settled nothing (the marker did not appear again, the two attempts disagreed) - not a refutation. `unverified`: nothing checked it, because there was nothing to check, reproduction never ruled, or the finding is a tester error. A wrong total or a select that resets is `unverified` by construction, not disproven. Default and safe reading when the block is absent. This enum drives an action - it is the ticket-filing gate, like `issue.severity` - so it stays closed for readers too and adding a member is a breaking change: a gate must refuse a value it cannot interpret rather than guess at one, which is why `marker_matched` made 0.5 a new family.

- `ValidationError` — shown above.

### Verdict1

**Type:** `string`

Known values, open (more may be added): `supported`, `unsupported`, `ungrounded`, `skipped`.

`supported`: the checker answered yes with a quote that gates against the ledger text of the cited steps - the only verdict that lets `status` read `verified`. `unsupported`: the checker answered no; the title claims more than the quoted line shows. `ungrounded`: the checker answered yes but its quote is not in the ledger, so the answer is discarded the way an ungrounded diary quote is and recorded in the gate ledger. `skipped`: no marker matched, or the check did not run. A classification: a later verdict is additive.

**Example:**

```json
"supported"
```

**Example:**

```json
"unsupported"
```

**Example:**

```json
"ungrounded"
```

**Example:**

```json
"skipped"
```

- `Verification` — shown above.
