> ## Documentation Index
> Fetch the complete documentation index at: https://docs.withboundary.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Results

> The ContractResult discriminated union

Every contract call returns a `ContractResult<T>` — a discriminated union on the `ok` field. Boundary never throws. You always get a structured result.

## Success: `result.ok === true`

```typescript theme={null}
if (result.ok) {
  result.data       // T — typed, validated, all rules passed
  result.attempts   // number — total attempts made (minimum 1)
  result.raw        // string — the raw LLM output that was accepted
  result.durationMS // number — total wall-clock time including retries
}
```

When `ok` is `true`, `data` is fully typed from your Zod schema. It has passed schema validation and every rule. Safe to use directly in your application.

## Failure: `result.ok === false`

```typescript theme={null}
if (!result.ok) {
  result.error.message   // string — human-readable summary
  result.error.attempts  // AttemptDetail[] — one per failed attempt
}
```

When `ok` is `false`, no data is returned to your application. The `error` object contains the full history of every attempt.

### AttemptDetail

Each failed attempt records:

```typescript theme={null}
type AttemptDetail = {
  raw: string                   // the raw LLM output
  cleaned: unknown              // the parsed/cleaned output
  issues: string[]              // human-readable violations (schema or rule)
  ruleIssues?: RuleIssue[]      // structured per-rule failures, only set on RULE_ERROR
  category: FailureCategory     // what type of failure
}

type RuleIssue = {
  rule: { name: string; fields?: string[] }
  message: string               // the failure string from rule.check (or rule.message)
}
```

`ruleIssues` is the typed companion to `issues` — same content, but with the rule's `name` and `fields` attached so you can route, count, or trigger logic per-rule on the receiving side.

## Pattern matching

### Basic

```typescript theme={null}
const result = await contract.accept(run);

if (result.ok) {
  await saveToDatabase(result.data);
} else {
  console.error("Contract failed:", result.error.message);
}
```

### Early return

```typescript theme={null}
const result = await contract.accept(run);
if (!result.ok) {
  return res.status(422).json({ error: result.error.message });
}
// result.data is typed T from here
await processLead(result.data);
```

### Logging attempts

```typescript theme={null}
if (!result.ok) {
  for (const attempt of result.error.attempts) {
    console.log(`Attempt failed [${attempt.category}]:`, attempt.issues);
  }
}
```

## Handling permanent failure

When `maxAttempts` is exhausted without an accepted output, you decide what happens next. The full attempt history is on `result.error.attempts` — use it to choose between retry-with-different-prompt, escalate-to-human, or fail-fast.

```typescript theme={null}
const result = await leadContract.accept(scoreLead);

if (result.ok) {
  await crm.updateLead(leadId, result.data);
  return;
}

// Pick the failure category from the last attempt — it tells you where to go.
const last = result.error.attempts.at(-1)!;

switch (last.category) {
  case "REFUSAL":
    // The model won't do this no matter how many times you ask.
    // Send to a human reviewer.
    await reviewQueue.add({ leadId, reason: "model refusal", attempts: result.error.attempts });
    break;

  case "RULE_ERROR":
    // The model produced structurally valid output but kept violating
    // your domain rules. Tag it for prompt iteration.
    for (const issue of last.ruleIssues ?? []) {
      metrics.increment("contract.rule_failure", { rule: issue.rule.name });
    }
    await reviewQueue.add({ leadId, reason: "rule violations exhausted", issues: last.issues });
    break;

  case "PARSE_ERROR":
  case "TRUNCATED":
  case "NO_JSON":
    // Format issues — usually a token-budget or prompt problem. Retry once
    // with a smaller schema or a higher max_tokens setting, otherwise drop.
    metrics.increment("contract.format_failure", { category: last.category });
    return res.status(502).json({ error: "model output unparseable" });

  case "RUN_ERROR":
    // Your RunFn threw — your provider call is broken, not the model.
    throw new Error(result.error.message);

  default:
    return res.status(422).json({ error: result.error.message });
}
```

The structured per-rule data on `ruleIssues` makes per-rule alerting cheap. Pair it with a metrics sink and you get a "which rule is degrading?" view with no extra plumbing.

## Failure categories

Every failed attempt is classified:

| Category           | Meaning                             |
| ------------------ | ----------------------------------- |
| `EMPTY_RESPONSE`   | Model returned nothing              |
| `REFUSAL`          | Model refused the task              |
| `NO_JSON`          | Response contained no JSON          |
| `TRUNCATED`        | JSON was cut off or incomplete      |
| `PARSE_ERROR`      | Malformed JSON                      |
| `VALIDATION_ERROR` | Valid JSON but failed Zod schema    |
| `RULE_ERROR`       | Passed schema but failed your rules |
| `RUN_ERROR`        | Your `RunFn` threw an error         |

The category tells you **where** the failure happened in the pipeline. `VALIDATION_ERROR` means the structure was wrong. `RULE_ERROR` means the structure was right but the values were wrong — this is the gap Boundary exists to close.

## Why not exceptions?

`ContractResult<T>` is the error handling path. No `try/catch` needed. If you want to throw:

```typescript theme={null}
const result = await contract.accept(run);
if (!result.ok) throw new Error(result.error.message);
```

But the result type gives you more: the full attempt history, failure categories, and specific violations. Exceptions throw that away.

## Next steps

<CardGroup cols={2}>
  <Card title="Guarantees" icon="shield-check" href="/concepts/guarantees">
    What Boundary promises when result.ok is true
  </Card>

  <Card title="When to Use" icon="compass" href="/concepts/when-to-use">
    Where Boundary fits — and where it doesn't
  </Card>
</CardGroup>

## Observability

Use [local development logging](/guides/local-development) to inspect individual rejected results while building. Use [production observability](/guides/production-observability) when you need acceptance rates, top failing rules, and recent rejected runs in the dashboard.
