# Get Retest

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

One retest of one issue: poll it after `POST .../retest` until `status` is `completed` or `failed`.
`result` then says whether the failure happened again, and the issue itself keeps what the run found.

## 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`
- **`retest_id` (required)**: `string`

## Responses

### 200 Successful Response

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

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`

**Example:**

```json
{
  "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.
- `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.
