contracts / errors.md

Validation errors

Catalog for the validator in F06. The same codes are returned for a proposal, a human save, an import, and the validate tool.

Eighteen rules come from the PRD validation table. Four rendering checks are warnings in the body. schema_invalid is the extra code from ADR-0015 for a type schema file that does not load.

Placeholders in a message or hint are filled by the validator. {field} is the frontmatter key. {line} is a 1-based line number in the file. {bytes} is an integer count of UTF-8 bytes. A character is one Unicode code point.

An issue is one object:

KeyValue
codeThe code in this catalog
severityerror or warning
fieldFrontmatter key, path, slug, content, base_revision_id, results.{key}, or the schema path
line1-based line when the issue is in the body or the YAML parser names a line. Omitted when the issue is only a field
messageThe sentence below, with placeholders filled
hintThe sentence below, with placeholders filled

valid is false when the result contains any error. Warnings leave valid true.

The 18 rules

CodeConditionSeverityField or lineMessageHint
yaml_invalidThe file has no frontmatter block, the block is not a YAML map, or the YAML parser throwsErrorLine 1, or the parser lineSee variantsSee variants
type_unknowntype is present and matches no schema that loaded from .ledger/types/ErrortypeUnknown type "{type}".Use a name that matches a loaded file in .ledger/types/.
field_missingA required field is absent or null, including a field whose required_when condition is trueError{field}{field} is required.See variants
field_kindThe value is the wrong YAML type, outside an enum, over a max, under a min, or not a real calendar dateError{field}See variantsSee variants
field_unknownA frontmatter key is not type and is not in the loaded schemaWarning{field}{field} is not a field on {type}.Remove {field}, or add it to the type schema.
link_brokenThe target does not resolve to a document under F03-REQ-001 (a page path, or a file name only one page has), archived documents includedError in frontmatter. Warning in the bodyField, or the body lineNo document matches "{target}".Link to a page path, or to a file name only one page has. Archived documents still count.
link_unpinnedA field with pinned: true is a wiki link and has no @versionError{field}{field} link needs a version.See variants
version_missingThe link names @version and no revision of that document has that versionErrorField, or the body line{slug} has no version {version}.Use a version saved on that document.
metric_unknownA key in results is not a metric key on the linked evalErrorresults.{key}{key} is not a metric on {eval}.Use a key from that eval's metrics list.
metric_missingstatus is concluded and a metric on the linked eval has no results keyWarningresultsresults is missing {key}.Report every metric the eval defines, including ones that got worse.
metric_rangeThe eval declares range and the result number is outside it, inclusive boundsErrorresults.{key}{key} must be between {min} and {max}.Use a value inside the range declared for {key}.
path_invalidThe path does not match the type folder and filename pattern, the date token cannot be filled, or the new path is already takenErrorpathSee variantsSee variants
slug_takenA create or a rename uses a slug that another document in the space already has, including an archived documentErrorslugSlug "{slug}" is already used.Use a free slug. The next suffix is -{n}.
base_missingA proposal update omits base_revision_id, or the id is not a revision of that documentErrorbase_revision_idUpdate needs a base revision.Send the revision id you read, as base_revision_id.
version_not_bumpedA harness or eval save changes the file and the new version is not an integer greater than the head's versionWarningversionversion is still {version} after a content change.Increase version. A pin keeps the first save of this version.
section_missingA name in the schema sections list has no matching heading in the bodyWarningbodyMissing section "{name}".Add a heading named {name}.
no_changeThe full file is byte-identical to the headErrorcontentContent is identical to the current head.Change the document, or skip this save.
too_largeThe UTF-8 byte length of the file is greater than 204800 bytes (200 KB, 200 × 1024)ErrorcontentDocument is {bytes} bytes. The limit is 204800 bytes (200 KB).Shorten the file to 204800 bytes or less, UTF-8.

A file of 204800 bytes is allowed. A file of 204801 bytes is too_large.

The 4 rendering checks

These are warnings. They do not block a save or a proposal. A broken diagram does not block a finding.

CodeConditionSeverityField or lineMessageHint
chart_invalidA chart fence fails the Vega-Lite v6 schema, or its data is not inline, a CSV under assets/, or a metrics queryWarningThe fence's first lineSee variantsSee variants
math_invalidKaTeX rejects inline math, a $$ block, or a math fenceWarningThe line where the math startsMath failed to parse: {parser}.Fix the KaTeX in this block.
embed_brokenThe next transclusion would pass depth 3, or the target is already in the embed chainWarningThe embed lineNot embedded ({reason}): {slug}.Open the source document.
mermaid_invalidMermaid rejects a mermaid fenceWarningThe fence's first lineDiagram failed to parse: {parser}.Fix the Mermaid syntax in this block.

chart_invalid and math_invalid are produced by the server validator. mermaid_invalid is produced when a diagram is rendered for reading or for review. The server validate tool does not run Mermaid.

{reason} is depth limit or cycle. Depth counts transclusions. The document on screen is not a level. Three nested embeds render. The fourth does not. A pinned embed counts as one level. The bytes a pin returns are the earliest revision with that version (ADR-0017). Which bytes render is F03. This code is only the warning.

schema_invalid

Not one of the 18. A file at .ledger/types/<name>.md that fails any check below does not load. Documents that name it then fail type_unknown on their next save. Existing documents are not rewritten.

Codeschema_invalid
SeverityError
FieldThe schema path, for example .ledger/types/experiment.md
BlocksThe save of that schema file
ConditionMessageHint
Frontmatter is missing or is not a YAML mapSchema frontmatter is not a YAML map.Start the file with ---, a YAML map, and a closing ---.
type is not schematype must be schema.Set type: schema in this file.
name fails the slug pattern or is outside 1 to 80 charactersname must match the slug pattern and be 1 to 80 characters.Use lowercase letters, digits, and hyphens. For example experiment.
folder is not one slug segment of 1 to 80 charactersfolder must be one slug segment, 1 to 80 characters.Use a single folder name, for example experiments.
folder is assetsfolder must not be assets.Pick a folder other than assets.
filename is empty, longer than 200 characters, or contains a {…} other than {date} and {slug}filename may only use the placeholders {date} and {slug}.Remove any other {token} from filename.
fields is not a mapfields must be a map of field names to kinds.Add a fields map. Each entry needs a kind.
A field name fails ^[a-z][a-z0-9_]*$ or is longer than 64 characters{field} is not a legal field name.Use a lowercase name that starts with a letter. Digits and underscores are allowed.
kind is not one of the nine kinds{field} kind must be one of string, number, date, enum, boolean, list, link, links, metrics.Set kind to one of those nine.
kind is enum and values is missing or empty{field} needs a non-empty values list.Add values as a list of strings.
kind is link or links and to is missing{field} needs a to type.Set to to a type name, for example harness.
kind is metrics and from is missing{field} needs a from field.Set from to the link field that points at the eval.
A key is present that the kind does not allow{field} uses keys its kind does not allow.Keep only the keys listed for that kind.
required_when is not a one-key map to a string, number, or boolean{field} required_when must be one field equal to a scalar.Use one key, for example status: concluded.
required_when names a field that is not on this schema{field} required_when names {watch}, which is not a field.Name a field on this schema.
required_when holds a scalar the watched field cannot hold{field} required_when value is not allowed for {watch}.Use one of the values {watch} allows.
The file stem differs from nameFile name must be {name}.md.Rename the file to match name, under .ledger/types/.
Another loaded file already uses namename "{name}" is already loaded from {other}.Keep one schema file for this name.

Kind keys that are allowed, besides kind, required, and required_when:

KindAlso allowed
stringmax (integer, at least 1, at most 100000), the unit is characters
numbermin (number, inclusive)
datenone
enumvalues (non-empty list of strings, each 1 to 80 characters, unique)
booleannone
listnone. Items are strings
linkto, pinned
linksto, pinned
metricsfrom

Variants

yaml_invalid

ConditionMessageHint
No opening --- block, or the parsed value is a list, a scalar, or nullFrontmatter is missing or is not a YAML map.Start the file with ---, then a YAML map that includes type and title, then a closing ---.
The YAML parser throwsFrontmatter YAML failed: {parser}.Fix the YAML between the --- lines. The block must be a map.

When this code is reported, field errors are not reported for that file. too_large is still reported when the byte length is over 204800 bytes.

field_missing

ConditionHint
The field is required: true, or it is type or titleAdd {field}.
required_when matchesAdd {field} when {watch} is {value}.

The message is always {field} is required.

type and title are required on every document even when fields omits them. A missing type is field_missing and is not also type_unknown.

field_kind

ConditionMessageHint
A string kind received a non-string{field} must be a string.Use a YAML string for {field}.
A string is empty and the minimum length is 1 character{field} must not be empty.Add text for {field}.
A string is longer than max characters{field} must be at most {max} characters.Shorten {field} to {max} characters or fewer.
A number kind received a non-number{field} must be a number.Use a YAML number for {field}, not a string.
A number is below min{field} must be at least {min}.Use a value of {min} or greater.
A date is not a real calendar date YYYY-MM-DD{field} must be a real date as YYYY-MM-DD.Use a calendar date, for example 2026-09-22.
An enum value is not in values{field} must be one of {values}.Pick one of the listed values.
A boolean kind received a non-boolean{field} must be true or false.Use a YAML boolean, not a string.
A list kind is not a list of strings{field} must be a list of strings.Use a YAML list of strings for {field}.
A link is not a wiki link{field} must be a wiki link.Use [[slug]], [[slug@7]], or [[slug#Heading]].
A links value is not a list of wiki links{field} must be a list of wiki links.Use a YAML list of [[slug]] strings.
A metrics value is not a map of keys to numbers{field} must be a map of metric keys to numbers.Set each metric to a YAML number.
An eval metric range has a first number greater than the second{field} range minimum is greater than its maximum.Put the lower bound first.
version is not an integer from 1 to 999999999version must be an integer from 1 to 999999999.Use a whole number, for example 7.
.ledger/space.md or .ledger/agents.md has type other than doctype must be doc.Use type: doc for space.md and agents.md.

A pinned field that is a wiki link without @version is link_unpinned only. It is not also field_kind.

{values} is the enum list, comma-separated, in schema order.

ConditionHint
The link's slug is a document whose status is active and whose version is an integerUse [[{slug}@{version}]]. Active version is {version}.
OtherwiseAdd @ and a version, for example [[slug@7]].

The message is always {field} link needs a version.

path_invalid

ConditionMessageHint
The path is not the type folder, the filename pattern, and .mdPath does not match {folder}/{pattern}.Save the file at {expected}.
The pattern contains {date} and date is missing or is not YYYY-MM-DDdate does not fit the filename pattern.Set date to YYYY-MM-DD, which the path uses.
The recomputed path is already another document's pathPath "{path}" is already used.Keep the current path, or pick a date and slug that are free.

For type doc, {expected} may include extra slug segments under docs/. .ledger/space.md and .ledger/agents.md do not use this code only because they sit under .ledger/.

chart_invalid

ConditionMessageHint
The fence body fails the Vega-Lite v6 schemaChart does not match the Vega-Lite schema.Fix the chart spec. Data must be inline, an assets/ CSV, or a metrics query.
Data comes from an external URL or another sourceChart uses a data source that is not allowed.Use inline data, a CSV in assets/, or a metrics query.

{parser} is the parser's own message, one line, truncated to 200 characters.

Precedence

  1. A schema file is checked only with schema_invalid. type_unknown does not apply to type: schema on .ledger/types/<name>.md.
  2. yaml_invalid suppresses field codes for that file.
  3. too_large is reported together with any other code that still applies.
  4. Null on a required field is field_missing only.
  5. Null on an optional field is treated as absent.
  6. A create does not report base_missing, version_not_bumped, or no_change.
  7. A human direct save does not report base_missing. That code is for a proposal update.
  8. link_broken looks at the target, resolved as in F03-REQ-001 (ADR-0035). A missing heading is not this code.
  9. An archived document is not link_broken.
  10. metric_range is skipped when the eval declares no range, and when the value is not a number (field_kind covers that).
  11. version_missing is skipped when the link has no @version (link_unpinned covers a pinned field).
  12. One condition produces one issue. A concluded experiment missing three metrics produces three metric_missing warnings, one per key.