# Findings API

The Findings API exposes Finding clusters, diagnostic settings, built-in Rules,
on-demand analysis Runs, time-window backfills, and provider-neutral repair work items.

Base URL:

```text
https://api.provon.dev/v1
```

Project API keys need `diagnostics:read` for read routes and `diagnostics:write` for mutation
routes. Signed-in Workbench sessions can use the same project-qualified routes when the user has
the corresponding project permission.

## Findings

| Method  | Path                                                                          | Purpose                                           |
| ------- | ----------------------------------------------------------------------------- | ------------------------------------------------- |
| `GET`   | `/projects/:projectId/diagnostics/findings`                                   | List Finding clusters                             |
| `GET`   | `/projects/:projectId/diagnostics/findings/:findingId`                        | Read one Finding and its primary work item        |
| `PATCH` | `/projects/:projectId/diagnostics/findings/:findingId`                        | Update review or resolution state                 |
| `POST`  | `/projects/:projectId/diagnostics/findings/discover`                          | Analyze the latest conversation activity snapshot |
| `GET`   | `/projects/:projectId/diagnostics/findings/:findingId/work-items`             | List work items for a Finding                     |
| `GET`   | `/projects/:projectId/diagnostics/findings/:findingId/work-items/:workItemId` | Read one work item                                |
| `POST`  | `/projects/:projectId/diagnostics/findings/:findingId/work-items`             | Create and confirm a repair handoff               |

List query parameters:

| Parameter            | Type                                                           | Notes                    |
| -------------------- | -------------------------------------------------------------- | ------------------------ |
| `reviewStatus`       | `unreviewed`, `confirmed`, or `dismissed`                      | Optional filter          |
| `resolutionStatus`   | `unresolved`, `awaiting_verification`, `resolved`, `regressed` | Optional filter          |
| `severity`           | `critical`, `high`, `medium`, `low`, `info`                    | Optional filter          |
| `minDiagnosticScore` | number from `0` to `1`                                         | Optional filter          |
| `lastDetectedAfter`  | Unix milliseconds                                              | Inclusive lower bound    |
| `lastDetectedBefore` | Unix milliseconds                                              | Exclusive upper bound    |
| `limit`              | integer from `1` to `500`                                      | Page size                |
| `cursor`             | opaque string                                                  | Cursor from prior result |

`lastDetectedAfter` must be earlier than `lastDetectedBefore`.

List responses include `findings`, each with its primary `workItem`, plus `pagination.limit` and
`pagination.nextCursor`.

### Read a Finding

```http
GET /v1/projects/:projectId/diagnostics/findings/:findingId
```

Response:

```json
{
  "id": "fnd_...",
  "clusterFingerprint": "...",
  "fingerprintVersion": "finding-cluster-v1",
  "reviewStatus": "unreviewed",
  "resolutionStatus": "unresolved",
  "severity": "high",
  "diagnosticScore": 0.85,
  "diagnosticConfidence": 0.82,
  "title": "Required verification was skipped",
  "summary": "The agent claimed completion without running the required verification.",
  "causeAssessment": "The first uncorrected fault is the omitted verification step.",
  "remediationHint": "Run and report the required verification before claiming completion.",
  "metadata": {
    "categoryKey": "goal_not_verified",
    "diagnosticRule": {
      "ruleId": "rule_...",
      "ruleKey": "task-fulfillment"
    }
  },
  "firstDetectedAt": 1760000000000,
  "lastDetectedAt": 1760000000000,
  "conversationId": "conv_123",
  "diagnosticAnchor": {
    "conversationId": "conv_123",
    "traceId": "trace_123",
    "spanId": "span_123",
    "stepIndex": 8,
    "quotes": [{ "quote": "I have completed the task." }]
  },
  "evidence": [
    {
      "ref": "span_123",
      "kind": "span",
      "title": "verify-answer",
      "summary": "Verification span was never executed.",
      "url": null,
      "location": {
        "traceId": "trace_123",
        "spanId": "span_123",
        "stepIndex": 8,
        "quotes": [{ "quote": "I have completed the task." }]
      }
    }
  ],
  "evidenceWindow": {
    "startMs": 1760000000000,
    "endMs": 1760000100000,
    "conversationIds": ["conv_123"]
  },
  "analysisRevision": "drv_...",
  "evidenceSnapshotRevision": "cesr_...",
  "evidenceThroughMs": 1760000100000,
  "createdAt": 1760000200000,
  "updatedAt": 1760000200000,
  "reviewedAt": null,
  "resolvedAt": null,
  "regressedAt": null,
  "workItem": null
}
```

### Update review or resolution

Supported `PATCH` fields (send one at a time):

```json
{
  "reviewStatus": "confirmed"
}
```

```json
{
  "resolutionStatus": "resolved"
}
```

`reviewStatus` can be `unreviewed`, `confirmed`, or `dismissed`. Manual resolution uses
`resolutionStatus: "resolved" | "unresolved"`. Dismissal atomically clears resolution state.
`reviewStatus` and `resolutionStatus` cannot be updated in the same request.

A Finding with an active work item (`provisioning`, `planned`, or `in_progress`) cannot be
dismissed or manually resolved. The API returns `409 ACTIVE_WORK_ITEM` or `409 WORK_ITEM_NOT_DONE`.

### Discover latest Findings

Queue diagnostic discovery for the latest activity snapshot of one conversation:

```http
POST /v1/projects/:projectId/diagnostics/findings/discover
```

Response:

```json
{
  "conversationId": "conv_123",
  "outcome": "findings_found",
  "findings": []
}
```

`outcome` is `findings_found` or `no_findings`. This endpoint is synchronous: it analyzes the
latest conversation snapshot and returns the produced Findings directly. It is not a backfill.

## Work Items

Work-item creation accepts `provider: "github" | "gitlab" | "linear" | "jira"` and an optional
provider `destination` object. The configured connector default is used when destination is omitted.

```http
POST /v1/projects/:projectId/diagnostics/findings/:findingId/work-items
```

```json
{
  "provider": "github",
  "destination": {
    "owner": "provon",
    "repo": "provon"
  }
}
```

Lifecycle actions:

```text
POST .../work-items/:workItemId/sync
POST .../work-items/:workItemId/retry
POST .../work-items/:workItemId/replacements
POST .../work-items/:workItemId/abandon
```

- **Retry** only works for a failed provisioning attempt (`status: provisioning`, `externalId: null`,
  `syncState: error`).
- **Replace** creates a new work item in another provider and accepts the same provider and
  destination body as creation.
- **Abandon** only detaches the local handoff; it does not close the external issue.
- **Sync** returns `{ outcome, finding }`. `outcome` can be `updated`, `unchanged`, `failed`,
  `not_syncable`, or `not_current`.

## Settings

| Method  | Path                                        | Purpose                                |
| ------- | ------------------------------------------- | -------------------------------------- |
| `GET`   | `/projects/:projectId/diagnostics/settings` | Read automatic scheduling settings     |
| `PATCH` | `/projects/:projectId/diagnostics/settings` | Enable or disable automatic scheduling |

Patch body:

```json
{
  "automaticSchedulingEnabled": true
}
```

## Rules

| Method  | Path                                                               | Purpose                                |
| ------- | ------------------------------------------------------------------ | -------------------------------------- |
| `GET`   | `/projects/:projectId/diagnostics/rules`                           | List built-in Rules and configuration  |
| `GET`   | `/projects/:projectId/diagnostics/rules/:ruleId`                   | Read one Rule                          |
| `PATCH` | `/projects/:projectId/diagnostics/rules/:ruleId`                   | Enable/disable or reconfigure one Rule |
| `POST`  | `/projects/:projectId/diagnostics/rules/:ruleId/backfill-requests` | Queue a time-window backfill           |

Patch either `enabled`:

```json
{
  "enabled": false
}
```

or `configuration`:

```json
{
  "configuration": {
    "keywords": ["checkout", "payment"],
    "minSignalScore": 0.25,
    "minDiagnosticConfidence": 0.5,
    "adjudicationMode": "auto",
    "adjudicationModel": null,
    "signalOverrides": {
      "runtime_error": { "enabled": true, "severity": "high" }
    }
  }
}
```

`adjudicationMode` can be `auto` or `deterministic-only`. Signal override severities can be
`critical`, `high`, `medium`, `low`, or `info`.

Backfill body:

```json
{
  "startMs": 1760000000000,
  "endMs": 1760086400000,
  "maxConversations": 100
}
```

Backfill responses use `202` when Runs are queued. Include an `Idempotency-Key` header to avoid
duplicate backfills for the same rule and window. The default `maxConversations` is 10,000 and the
maximum is 100,000.

## On-Demand Runs

Queue enabled Rules for one conversation activity snapshot:

```http
POST /v1/projects/:projectId/diagnostics/analysis-runs
```

```json
{
  "conversationId": "conv_123",
  "lastActivityAt": 1760000000000,
  "traceIds": ["trace_123"]
}
```

`conversationId` and `lastActivityAt` are required. `traceIds` is optional and is filtered to
string values. Response:

```json
{
  "enabledRules": 5,
  "runsCreated": 1
}
```

## Common Errors

| HTTP  | Code                               | When                                                                                    |
| ----- | ---------------------------------- | --------------------------------------------------------------------------------------- |
| `400` | `INVALID_FILTER`                   | Unsupported enum value, malformed cursor, or `lastDetectedAfter >= lastDetectedBefore`. |
| `404` | `FINDING_NOT_FOUND`                | Finding or referenced work item does not exist.                                         |
| `404` | `DIAGNOSTIC_RULE_NOT_FOUND`        | Rule does not exist.                                                                    |
| `404` | `DISCOVERY_CONVERSATION_NOT_FOUND` | No recent conversation found for discovery.                                             |
| `409` | `DISCOVERY_CONVERSATION_NOT_READY` | Latest conversation is not ready for analysis.                                          |
| `409` | `ACTIVE_WORK_ITEM`                 | Cannot dismiss a Finding with an active work item.                                      |
| `409` | `WORK_ITEM_NOT_DONE`               | Cannot resolve a Finding whose work item is not done.                                   |
| `409` | `WORK_ITEM_ALREADY_EXISTS`         | A primary work item already exists for this Finding.                                    |
| `409` | `WORK_ITEM_NOT_CURRENT`            | The work item was replaced or is no longer primary.                                     |
| `409` | `WORK_ITEM_NOT_RETRYABLE`          | The work item is not a failed provisioning attempt.                                     |
| `409` | `FINDING_DISMISSED`                | Cannot create a work item for a dismissed Finding.                                      |
| `503` | `WORK_ITEM_PROVIDER_UNAVAILABLE`   | Connector is missing, disabled, or default destination is unset.                        |
| `503` | `DIAGNOSTIC_RULES_UNAVAILABLE`     | Diagnostic rule service is not mounted in this deployment.                              |
| `503` | `FINDING_WORK_ITEMS_UNAVAILABLE`   | Work item service is not mounted in this deployment.                                    |
