= Cutdown Diagnostics Policy

== Goal

Diagnostics must be machine-readable, stable, and non-blocking for normal authoring workflows.

== Output Contract

A parser returns:

- ``ast``
- ``diagnostics[]``

Each diagnostic includes:

- ``code`` (stable identifier, for example ``CDN-XXXX``)
- ``level`` (``error`` \| ``warning`` \| ``info``)
- ``message`` (human-readable)
- ``loc`` (``{ file?, start, end }`` — raw-file UTF-16 code-unit offsets, end-exclusive, per spec [§14](/latest/#s-14) Location Type; line/column presentation is derived by the reporting tool)
- ``recovery`` (how parsing proceeded)

== Severity Rules

- ``error``: malformed or invalid input requiring fallback/recovery, but parser still returns AST.
- ``warning``: valid parse with potentially unintended author input.
- ``info``: non-problem metadata signals.

The parser must not fail-fast on the first issue. It must continue with deterministic recovery and report all encountered diagnostics for the parse pass.

== Recovery Rules

- Recovery behavior must be deterministic and documented per diagnostic code.
- If a construct is invalid/unclosed, parser follows the spec fallback rule and emits corresponding diagnostics.
- Recovery must preserve as much surrounding valid structure as possible.

== Stability Requirements

- Diagnostic ``code`` values are part of the language contract.
- Changing meaning of an existing diagnostic code requires at least a ``minor`` policy review and migration note.
- Removing a diagnostic code requires major-version justification.

Diagnostic code definitions and lifecycle are governed by ``./diagnostic-code-registry-policy.md``.

== Conformance Requirements

Conformance tests must assert:

- expected AST output,
- expected diagnostic codes and levels,
- expected recovery mode for malformed cases.

== Silent-Drop Warning Cases

The following spec-defined recovery behaviors MUST emit a ``warning``-level diagnostic. See the Diagnostic Code Registry for full trigger/recovery definitions.

| Situation | Code | Recovery |
|---|---|---|
| Unclosed CodeBlock fence (``\`\`\```) | CDN-0001 | Content runs to end of document |
| Unclosed MetaBlock fence (``~~~``) | CDN-0002 | Content runs to end of document |
| Unclosed MathBlock fence (``$$$``) | CDN-0003 | Content runs to end of document |
| Unclosed NamedBlock (``:::name``) | CDN-0004 | Content runs to end of document |
| PageBreaker tail dropped | CDN-0016 | Everything after the leading ``---`` is discarded; page boundary takes effect |
| Page separator inside block container | CDN-0017 | ``---`` line emitted as literal ``Paragraph``; no page boundary |
| Excess scope-chain ``{...}`` orphaned | CDN-0011 | Excess ``{...}`` at front of chain discarded; no AST output |
| Heading level > 9 (10+ ``=`` signs) | CDN-0012 | Entire line emitted as literal ``Text`` |
| ``:::`` not followed by ``[ID_LITERAL]`` (nameless opener) | CDN-0013 | Block candidate parsed as Paragraph; ``:::`` and ``{attrs}`` emitted as literal text |
| Duplicate ``id`` token (``#id`` or ``id=`` after first claim) | CDN-0020 | Duplicate dropped; first value kept |
| ``class=`` alongside ``.class`` syntax | CDN-0021 | ``class=`` dropped; ``.class`` tokens kept |
| Duplicate custom attribute key | CDN-0022 | Duplicate dropped; first value kept |
| ``~~~`` fence inside a block container | CDN-0030 | Raw span (including fence lines) emitted as literal ``Paragraph``; no ``Meta`` node created |
| Crossed inline boundaries (``** __ … ** … __``) | CDN-0014 | Greedy parse unchanged; diagnostic only — span on the crossing closer |
| ``Mark`` opener beyond nesting depth 8 | CDN-0031 | Opener degrades to literal ``Text("::" + name)``; enclosing ``Mark`` nodes unaffected |

Strict parser profiles (per ``./parser-profile-policy.md``) MAY upgrade any ``warning`` to ``error``.

== Related

- [``./conformance-policy.md``](/latest/policies/conformance-policy)
- [``./parser-profile-policy.md``](/latest/policies/parser-profile-policy)
- [``./diagnostic-code-registry-policy.md``](/latest/policies/diagnostic-code-registry-policy)
