> ## 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.

# BoundaryLogEvent

> The wire format sent to the ingest endpoint

Every contract run produces one or more `BoundaryLogEvent`s — one per attempt that fails on a retry, plus a terminal event when the run finishes. This is the shape that hits the network (or your custom `write` sink).

```typescript theme={null}
import type { BoundaryLogEvent } from "@withboundary/sdk";
```

## Shape

`BoundaryLogEvent` is a tagged union on `ok`. Successful runs always emit a single terminal event; failed runs emit one event per non-terminal attempt plus a terminal event when retries are exhausted.

```typescript theme={null}
// Common base — every event carries identity + run metadata + SDK attribution.
interface BoundaryLogEventBase {
  // identity
  contractName: string;
  environment?: string;
  timestamp: string;          // ISO 8601
  runId: string;              // bnd_run_<nanoid>, stable across all events for a run

  // run metadata (always sent)
  attempt: number;
  maxAttempts: number;
  durationMs: number;
  model?: string;
  rulesCount?: number;
  schema?: SchemaField[];
  rules?: RuleDefinition[];

  // SDK attribution (always sent)
  sdk?: { name: string; version: string; runtime?: string };

  // Resolved capture policy stamped on every event
  capture?: CapturePolicy & { redactedFields?: string[] };

  // raw data — gated by capture.inputs / capture.outputs (default off)
  input?: unknown;
  output?: unknown;
}

// An attempt that satisfied schema and every rule. Always terminal.
interface AcceptedEvent extends BoundaryLogEventBase {
  ok: true;
  final: true;
}

// An attempt that didn't pass. final=false is mid-run; final=true is terminal.
interface FailedEvent extends BoundaryLogEventBase {
  ok: false;
  final: boolean;
  category: string;            // RULE_ERROR, PARSE_ERROR, REFUSAL, ...
  issues: string[];
  ruleFailures?: string[];     // names of rules that failed this attempt
  repairs?: Array<{ role: string; content: string }>;  // gated by capture.repairs
}

type BoundaryLogEvent = AcceptedEvent | FailedEvent;
```

## Field-level reference

### Identity

| Field          | Set by   | Notes                                                                                                                                                                                                      |
| -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `contractName` | Contract | The `name` field you passed to `defineContract`. Required.                                                                                                                                                 |
| `environment`  | Logger   | From `createBoundaryLogger({ environment })`. Buckets data on the dashboard.                                                                                                                               |
| `timestamp`    | SDK      | ISO 8601, stamped at emit time.                                                                                                                                                                            |
| `runId`        | SDK      | `bnd_run_<nanoid>`, generated once per `accept()` call, stamped on every event for that run. The backend coalesces per-attempt + terminal events into a single run row keyed by `(organizationId, runId)`. |

### Run metadata (always sent)

| Field         | Meaning                                                                                                                                               |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `attempt`     | The attempt this event reflects. On a non-terminal failure event it's the attempt that just failed; on a terminal event it's the final attempt count. |
| `maxAttempts` | From the contract's retry config at the time of the run.                                                                                              |
| `durationMs`  | Per-attempt event: duration of just this attempt. Terminal event: total wall-clock across all attempts.                                               |
| `ok`          | Discriminator — `true` on AcceptedEvent, `false` on FailedEvent.                                                                                      |
| `final`       | `true` on terminal events (run is over), `false` on mid-run failure events. AcceptedEvent is always `final: true`.                                    |
| `model`       | Per-call override (`contract.accept(run, { model })`), else the logger's default `model`, else undefined.                                             |
| `rulesCount`  | Number of rules on the contract at runtime.                                                                                                           |

### Failure attribution (always sent on `FailedEvent`)

There is **no `capture.errors` flag** — these are structural fields that always appear when `ok: false`. Without them a failure event is just a flag.

| Field          | Meaning                                                                                                               |
| -------------- | --------------------------------------------------------------------------------------------------------------------- |
| `category`     | `EMPTY_RESPONSE`, `REFUSAL`, `NO_JSON`, `TRUNCATED`, `PARSE_ERROR`, `VALIDATION_ERROR`, `RULE_ERROR`, or `RUN_ERROR`. |
| `issues`       | Schema errors or rule violations from the failed attempt.                                                             |
| `ruleFailures` | Names of the rules that failed this attempt. Maps to the dashboard's per-rule failure attribution.                    |

If a failure message would leak data, fix it at the source — rewrite the rule's failure message, or use [`beforeSend`](/sdk/before-send) to scrub `issues` before the event leaves.

### Repair context

Gated by `capture.repairs` (default on). Present only on `FailedEvent` with `final: false` — the repair message is the prompt that's about to be sent before the next attempt.

```typescript theme={null}
repairs: [
  { role: "user", content: "hot leads require score > 70, got 25" }
]
```

### Raw data (opt-in)

| Field    | Gate                               |
| -------- | ---------------------------------- |
| `input`  | `capture.inputs` — off by default  |
| `output` | `capture.outputs` — off by default |

Treat these as sensitive. See [Capture policy](/sdk/capture-policy) and [Redaction](/sdk/redaction).

### SDK attribution

Stamped automatically. Used for attribution on the dashboard and for debugging version-specific behavior. `runtime` is `"node/<version>"` on Node, `"browser"` where `navigator.userAgent` exists, or omitted on edge runtimes.

## Lifecycle

```mermaid theme={null}
flowchart LR
    run[Contract run ends] --> build[logger builds event]
    build --> capture[applyCapture strips buckets]
    capture --> redact[redact walks leaves]
    redact --> before[beforeSend hook]
    before --> queue[Batcher queue]
    queue --> flush[flush triggers]
    flush --> http[HTTP POST /v1/ingest]
```

Each hop runs **before** the event is visible on the next. If capture strips a field, it can't be added back by `beforeSend` for that event.

## Example success event

```json theme={null}
{
  "contractName": "invoice-extraction",
  "environment": "production",
  "timestamp": "2026-04-20T09:45:12.104Z",
  "runId": "bnd_run_8t2k7n9p4qz3v1m6h5b8x",
  "attempt": 2,
  "maxAttempts": 3,
  "durationMs": 2104,
  "model": "gpt-4.1",
  "rulesCount": 2,
  "ok": true,
  "final": true,
  "sdk": {
    "name": "@withboundary/sdk",
    "runtime": "node/22.12.0"
  }
}
```

## Example failure event (terminal)

```json theme={null}
{
  "contractName": "agent-action-validation",
  "environment": "production",
  "timestamp": "2026-04-20T09:47:03.221Z",
  "runId": "bnd_run_q9w7e3r5t6y8u1i2o4p3a",
  "attempt": 3,
  "maxAttempts": 3,
  "durationMs": 5829,
  "model": "claude-haiku",
  "rulesCount": 4,
  "ok": false,
  "final": true,
  "category": "RULE_ERROR",
  "issues": ["cannot close a ticket with status needs_review"],
  "ruleFailures": ["cant_close_in_review"],
  "sdk": { "name": "@withboundary/sdk", "runtime": "node/22.12.0" }
}
```

## See also

<CardGroup cols={2}>
  <Card title="Capture policy" icon="filter" href="/sdk/capture-policy">
    Three optional buckets, plus the always-on fields
  </Card>

  <Card title="createBoundaryLogger" icon="code" href="/api-reference/create-boundary-logger">
    Full options reference
  </Card>
</CardGroup>
