# Diagnostics

## Shape

- A diagnostic has a code, severity, optional source span, and typed arguments.
- Source spans identify a file name, start position, and end position.
- Positions are zero-based and include byte offset, line, and column.

## Message Localization

- Every user-visible diagnostic message must be defined in a locale catalog.
- Implementation code must not embed user-visible diagnostic message strings.
- Diagnostic codes, constants, severities, and argument schemas are defined in a diagnostic registry.
- Locale catalogs contain only diagnostic code and localized message template columns.
- The English locale is required during development.
- Other locales, including Japanese, must be addable without changing diagnostic call sites.
- Diagnostics store code, severity, span, and typed arguments.
- Diagnostic reporters render localized messages from the locale catalog.
- Diagnostics that are not tied to source text may omit the source span.
- Catalog templates use `{name}` placeholders for arguments.
- All placeholders used by a localized message must be declared by that diagnostic code's argument schema.
- All diagnostic codes must have an English message.
- Missing messages, unknown message codes, or mismatched placeholders are errors in generation or tests.
- Diagnostic code catalogs are stable user-visible contracts.
- Adding, removing, or renaming a diagnostic code is a user-visible behavior change.
- At this stage, diagnostic argument schemas only define string arguments.

## Reporters

- Text reporters write one localized diagnostic message per line.
- JSON reporters write an object with a `diagnostics` array.
- Each JSON diagnostic entry contains `code`, `severity`, `message`, optional `span`, and optional `args`.
- When there are no diagnostics, JSON reporters write an empty `diagnostics` array.

## Severities

- `error`: the source cannot be accepted for the requested operation.
- `warning`: the source can be accepted, but the behavior should be reviewed.
- `info`: informational output.
- `hint`: editor-oriented guidance.

## Master Validation Diagnostics

The master [validation](../masterdata/validation.md) feature registers the following diagnostic codes.

### Validator Evaluation

| Code | Severity | Meaning | Arguments |
| --- | --- | --- | --- |
| `masterbelt.validation.assert_failed` | configured (default `error`) | An `assert` condition evaluated `false`. The span is the condition expression. | `master`, `validator`, `scope` (`each`/`all`), `record`, `expr` |
| `masterbelt.validation.evaluation_failed` | error | A validation rule body raised a hard evaluation error. | `master`, `validator`, `scope`, `detail` |

`masterbelt.validation.assert_failed` is emitted at the severity resolved from project configuration: the default is `error`, and a `(master, validator)` override to `warning` is reflected in the emitted diagnostic. The `master` argument is the entrypoint-visible master path; the `record` argument is the failing record's primary-key description for an `each` failure and `<table>` for an `all` failure; `expr` is the condition's source text.

### Validator Configuration

| Code | Severity | Meaning | Arguments |
| --- | --- | --- | --- |
| `masterbelt.validation.config_unknown_master` | error | A `validators` config key names a master that does not exist. | `master` |
| `masterbelt.validation.config_unknown_validator` | error | A `validators` config key names a validator that does not exist on a known master. | `master`, `validator` |
| `masterbelt.validation.config_invalid_severity` | error | A `validators` severity is not `error` or `warning`. | `master`, `validator`, `severity` |
| `masterbelt.validation.ambiguous_master` | error | A master is re-exported from the entry module under more than one name, so no config path identifies it unambiguously; its validators are skipped. | `master`, `aliases` |

A master that the entry module neither declares nor re-exports has no entrypoint-visible config path, so its validators are out of scope and run no diagnostics.

### Checker

| Code | Severity | Meaning | Arguments |
| --- | --- | --- | --- |
| `masterbelt.checker.validator_duplicate` | error | A duplicate validator id within a master. | `master`, `name` |
| `masterbelt.checker.assert_outside_validation` | error | `assert` used outside a validation block. | — |
| `masterbelt.checker.return_in_validation` | error | `return` used inside a validation block. | — |
| `masterbelt.checker.assert_condition_non_bool` | error | An `assert` condition is not `bool`. | `actual` |

### Parser

| Code | Severity | Meaning |
| --- | --- | --- |
| `masterbelt.parser.assert_missing_condition` | error | An `assert` statement has no condition expression. |
| `masterbelt.parser.master_validation_group_missing_scope` | error | A validation group is missing its `each` / `all` scope. |
| `masterbelt.parser.master_validation_rule_missing_name` | error | A `validate` rule is missing its identifier. |
| `masterbelt.parser.master_validation_rule_missing_body` | error | A `validate` rule is missing its body block. |
| `masterbelt.parser.unexpected_master_validation_node` | error | An unexpected node appeared inside a `validation` section. |

A duplicate `validation` section within a master reuses the existing `masterbelt.parser.master_section_duplicate` code.

## Scope Diagnostics

The master [scope section](../masterdata/schema.md#scope-section) and the SQLite indexed-scope inference contribute the following diagnostics.

### Parser

| Code | Severity | Meaning |
| --- | --- | --- |
| `masterbelt.parser.master_scope_missing_name` | error | A `scope` declaration has no name. |
| `masterbelt.parser.master_scope_missing_body` | error | A `scope` declaration has no body. |

### Checker

| Code | Severity | Meaning | Arguments |
| --- | --- | --- | --- |
| `masterbelt.checker.duplicate_scope` | error | A duplicate scope name within one master. | `master`, `name` |
| `masterbelt.checker.scope_name_conflict` | error | A scope name collides with a relation method, nested master, static, function, or const. | `master`, `name` |
| `masterbelt.checker.scope_return_type_mismatch` | error | A scope body returns something other than the declaring master's `Relation<M>`. | `master`, `actual` |
| `masterbelt.checker.scope_missing_return` | error | A block-body scope has no `return`. | `master`, `name` |
| `masterbelt.checker.scope_forbidden_effect` | error | A scope body or query callback inherits `failable` / `cancellable` / `asyncable`. | `master`, `name`, `effect` |
| `masterbelt.checker.cyclic_scope` | error | A scope references itself directly or transitively. The span is the cycle-closing call's callee. | `master`, `name` |
| `masterbelt.checker.scope_unknown_field` | error | A query callback references a field the record does not declare. | `master`, `field` |

Some scope misuse reuses existing diagnostics rather than a scope-specific code: declaring or assigning `self` is reported as `masterbelt.parser.reserved_identifier` because `self` is lexically reserved (see [lexical.md](lexical.md#keywords)); a scope call with the wrong argument count or types is reported through the general call diagnostics (`masterbelt.checker.call_argument_count_mismatch`, `masterbelt.checker.call_argument_type_mismatch`); and a scope call on a receiver that does not expose the scope is reported as `masterbelt.checker.unknown_member`.

### SQLite Index Inference

| Code | Severity | Meaning | Arguments |
| --- | --- | --- | --- |
| `masterbelt.scope.index_inference_failed` | warning | An `indexed scope` could not be fully turned into a secondary index; any inferable part is still generated. | `master`, `scope` |
| `masterbelt.scope.index_generated` | info | A secondary index was generated from an `indexed scope`. | `master`, `scope`, `index` |
