# Diagnostics

URL: /docs/agents/diagnostics
Markdown: /docs/agents/diagnostics.md

> Reference for the JSON Lines diagnostics produced by sakura check --json, including record kinds, error codes, and fix impact classes.

> **Pre-alpha**
>
> The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates.

Diagnostics are an API. Every diagnostic has a stable code, byte spans, structured expected and actual values, causal links, and fixes that declare their impact. The CLI, the LSP server, and the [MCP server](/docs/agents/mcp) share one analysis library and one format.

```bash
sakura check --json
sakura test --json
```

`--json` writes **JSON Lines**: one record per line. Human-readable output is rendered from the same data.

## Record kinds [#record-kinds]

| `kind` | Meaning |
| --- | --- |
| `diagnostic` | An error, warning, or note. Shape below. |
| `hole` | A [typed hole](/docs/agents/typed-holes) with its context. |
| `obligation` | A claim about the program and its status: `proved`, `runtime_checked`, `tested`, or `trusted`. |
| `progress` | Progress of a long run. |
| `summary` | End of run. `summary.truncated` is `true` if output hit the cap. |

## The diagnostic record [#the-diagnostic-record]

The program below omits `Console` from `greet`'s effect row:

```sakura
fn greet(name: String) -> Unit {
  Console.print("hello, {name}")
}
```

`sakura check --json` would report it as one line, shown here pretty-printed. Hashes and ids are shortened.

```json
{
  "schema_version": "1.0",
  "kind": "diagnostic",
  "id": "d:7f3a",
  "code": "V3001",
  "severity": "error",
  "phase": "effect",
  "message": "function `greet` performs `Console`, which its signature does not allow",
  "snapshot": "sha256:91c0",
  "symbol_id": "hello::main::greet",
  "spans": [
    { "uri": "file:///work/hello/src/main.sakura", "content_hash": "sha256:4be2",
      "start_byte": 3, "end_byte": 8, "role": "primary", "label": "`greet` is declared pure" },
    { "uri": "file:///work/hello/src/main.sakura", "content_hash": "sha256:4be2",
      "start_byte": 35, "end_byte": 65, "role": "cause", "label": "`Console.print` introduces `Console` here" }
  ],
  "related_ids": [],
  "expected": { "render": "{}", "row": { "labels": [], "tail": null } },
  "actual": { "render": "{Console}", "row": { "labels": [{ "effect": "std::Console", "args": [] }], "tail": null } },
  "obligation": null,
  "counterexample": null,
  "explanation_uri": "sakura://explain/V3001",
  "fixes": [
    { "id": "f:01", "title": "Add `Console` to the signature of `greet`",
      "applicability": "suggested", "impact": "interface_change",
      "edits": [ { "span": { "uri": "file:///work/hello/src/main.sakura", "content_hash": "sha256:4be2",
                             "start_byte": 30, "end_byte": 30, "role": "primary", "label": "" },
                   "replacement": " ! {Console}" } ] }
  ]
}
```

For `V3001`, the design specifies two fixes: add the effect to the signature (`interface_change`) or handle it locally with `with` (`local`). Only the first is shown.

## Fields [#fields]

| Field | Type | Meaning |
| --- | --- | --- |
| `schema_version` | string | Version of this format. `"1.0"` in v0. |
| `kind` | string | `"diagnostic"`. |
| `id` | string | Unique per occurrence in this run. |
| `code` | string | Stable class, such as `V3001`. See [codes](#error-codes). |
| `severity` | string | `error`, `warning`, or `note`. |
| `phase` | string | `lex`, `parse`, `resolve`, `type`, `effect`, `pattern`, `contract`, `capability`, `verify`, `package`, or `internal`. |
| `message` | string | One sentence naming the symbols involved in backticks. |
| `snapshot` | string | Hash of the workspace snapshot that was checked. |
| `symbol_id` | string or null | The symbol the diagnostic is about. |
| `spans` | array | Source locations, each with `uri`, `content_hash`, `start_byte`, `end_byte`, `role`, `label`. |
| `related_ids` | array | Ids of root-cause diagnostics this one cascades from. Empty for a root cause. |
| `expected` | object or null | What was required: a `render` string, and a structured `row` for effect rows. |
| `actual` | object or null | What was found, in the same shape. |
| `obligation` | object or null | `id`, `clause` (for example `"ensures bounded"`), and `status`. |
| `counterexample` | object or null | `inputs` and a `trace`, when one is known. |
| `explanation_uri` | string | `sakura://explain/<code>`. Same content as `sakura explain <code>` and the MCP `explain_diagnostic` tool. |
| `fixes` | array | Zero or more fixes. See below. |

Span `role` is one of `primary`, `cause`, `definition`, or `use`.

### Rules [#rules]

- Byte offsets are zero-based, half-open, and count UTF-8 bytes. There are no line or column numbers.
- `content_hash` must match the file before any edit is applied.
- The edits in one fix are applied atomically and rejected if they overlap.
- Records are ordered root causes first (empty `related_ids`), then by position.
- Output is capped at 200 diagnostics per run, with `summary.truncated: true`.

## Fixes [#fixes]

| Field | Meaning |
| --- | --- |
| `id` | Fix id, accepted by `sakura fix --apply`. |
| `title` | Short imperative description. |
| `applicability` | `machine_applicable`, `suggested`, or `incomplete`. |
| `impact` | What the fix changes. See below. |
| `edits` | List of `{ span, replacement }`. A span with equal start and end is an insertion. |

### Impact classes [#impact-classes]

| `impact` | Changes | Suggested agent policy |
| --- | --- | --- |
| `local` | Code inside one body; no signature changes. | Apply if `machine_applicable`. |
| `behavior_change` | What the code does, without changing its signature. | Apply only if it matches the task. |
| `interface_change` | A signature, effect row, or error union. | Apply only if the task needs it, and report it for review. |
| `contract_change` | A `requires`, `ensures`, or `where` clause. | Never apply unless a human asks. |
| `authority_change` | Permissions or capabilities. | Never apply unless a human asks. |

Compiler fixes with `interface_change`, `contract_change`, or `authority_change` impact are never `machine_applicable`. No fix ever widens authority or weakens a contract by itself.

`sakura fix` lists fixes with their impact class. It applies only `machine_applicable` fixes unless given ids with `--apply id,…`, and it never applies `contract_change` or `authority_change`. `--preview` shows the edits without writing.

### Frozen specs [#frozen-specs]

`sakura check --frozen-specs <symbols|all>`, `sakura fix`, and the MCP `apply_patch` tool reject any patch that changes a frozen symbol's signature, effect row, error union, `requires`/`ensures`/`where` clauses, or any `test`/`property` body. The diagnostic is `V8020 spec_frozen` and names the clause. This stops an agent from passing a check by editing the spec.

## Error codes [#error-codes]

Codes are `V` + a phase digit + three digits for errors, `W` for warnings, and `D` for runtime defects. Codes are never reused or renumbered; retired codes stay documented. Every code has an explain page with a minimal pair: wrong program, diagnostic, fixed program.

| Phase digit | Area | Examples |
| --- | --- | --- |
| `0` | Lexing and parsing | `V0001` syntax error, `V0007` reserved keyword, `V0010` chained comparison, `V0011` call-site type arguments, `V0014` named argument |
| `1` | Resolution and modules | `V1002` unresolved name (offers the import as a fix), `V1004` wrong casing, `V1006` item not `pub` or opaque constructor, `V1007` wrong kind of name, `V1010` unknown attribute, `V1030` import cycle, `V1040` assignment to a `let`, `V1041` jump outside a loop |
| `2` | Types | `V2031` `Secret` is not `Show`, `V2032` constraint not satisfied, `V2040` unused value, `V2041` lambda or `try` body assigns an outer `var`, `V2050` hole in build |
| `3` | Effects and handlers | `V3001` effect not allowed, `V3011` handler clauses do not match the operations, `V3020` test needs a handler |
| `4` | Patterns | `V4001` non-exhaustive match, `V4002` redundant arm |
| `5` | Contracts and refinements | `V5001` predicate outside the contract subset |
| `6` | Capabilities, resources, escape | `V6002` capability escapes its scope, `V6003` resource escapes, `V6010` non-std effect in `main` |
| `7` | Tests and verification | None named in the design yet |
| `8` | Packages, build, frozen specs | `V8020` spec frozen |
| `9` | Internal | Compiler bugs |

Warnings named in the design include `W1002` (a local shadows an import), `W4010` (catch-all `_` on an open error union), `W5005` (an O(n) contract on a hot path), `W7001` (property generator discard budget exceeded), and `W8001` (a collection update copies in a loop).

Runtime defects are not diagnostics from `sakura check`. They terminate the task; if `main` defects, the program writes one `defect` record to stderr and exits with status 70:

```json
{"schema_version":"1.0","kind":"defect","code":"D002","message":"division by zero"}
```

| Code | Raised by |
| --- | --- |
| `D001` | `Int` overflow |
| `D002` | `/` or `%` by zero |
| `D003` | `Int` beyond ±(2^53−1) on the JavaScript backend |
| `D004` | `Float.round` or `Float.to_int_checked` of NaN or ±∞ |
| `D010` | failed refinement in a checked introduction (`T.from`) |
| `D011` | a contract predicate itself defected |
| `D020`, `D021` | `requires` violated (caller blamed), `ensures` violated (callee blamed) |
| `D030` | `Option.expect` on `None` |
| `D031` | `String.slice` off a character boundary |
| `D090`, `D091` | runtime-internal error, host operation failed |

## Exit codes [#exit-codes]

| Code | Meaning |
| --- | --- |
| `0` | OK. |
| `1` | Diagnostics with errors, or `main` ended with an uncaught `Fail` (stderr: `error: main failed with <Show of the error>`). |
| `2` | Usage error. |
| `3` | Test failures. |
| `70` | Defect at runtime. |
| `75` | Permission denied at startup. |

## Next [#next]

[Typed holes](/docs/agents/typed-holes): writing a program one `?hole` at a time.
