Skip to content

Validation Errors [[validation_errors: SyntaxConcept]]SyntaxConcept

Validation errors detected by the workspace validate command.

A [[#id]] reference points to a non-existent object in the workspace (after both __id and __local_id lookups fail).

  • code: broken_link
  • severity: error

Cause [[cause: text]]text

  • The object with the specified ID does not exist (neither as __id nor as __local_id)
  • Typo in the reference ID
  • The object was deleted but references remain
  • Incorrect namespace in the reference

Note: before producing a broken_link error, the validator attempts __local_id fallback — if the reference matches exactly one object's __local_id, it resolves successfully without error.

Solution [[solution: text]]text

  1. Verify the target object exists
  2. Fix the ID in the reference
  3. Create the missing object
  4. Add a qualifier to the reference: [[#namespace:id]] or [[#workspace:namespace:id]]
  5. Use the full hierarchical ID: [[#parent.child]] instead of [[#child]]

Duplicate ID [[err_duplicate_id: ValidationError]]ValidationError

Two objects with the same Kind:Id in one namespace. Every occurrence after the first is reported, and candidates lists all of them, in the order the files are read: directory, then readme.qmd.md, then file name, each compared by UTF-8 bytes.

  • code: duplicate_id
  • severity: error

Cause [[cause: text]]text

  • Copying objects without changing the ID
  • Auto-generated IDs from identical Titles
  • Git merge conflicts

Solution [[solution: text]]text

  1. Rename one of the objects (change the ID)
  2. Use explicit [[id]] instead of auto-generation
  3. Move objects into different namespaces
  4. Change the Kind of one of the objects

Ambiguous Reference [[err_ambiguous_ref: ValidationError]]ValidationError

A [[#id]] reference could point to multiple objects (ID collision or multiple __local_id matches).

  • code: ambiguous_reference
  • severity: error

Cause [[cause: text]]text

  • Same ID in different namespaces
  • Reference without a namespace qualifier
  • Multiple objects share the same __local_id (e.g., several child objects named [[config]] under different parents, all with __local_id: "config")

Two objects with the same ID in ONE namespace are a duplicate_id, whatever their Kinds.

Solution [[solution: text]]text

  1. Add namespace to the reference: [[#storage:users]]
  2. Use the full form: [[#workspace:storage:users]]
  3. Use the full hierarchical ID: [[#parent.config]] instead of [[#config]]

Nested Workspace [[err_nested_workspace: ValidationError]]ValidationError

A workspace inside another workspace, left out of the result: its files are not part of the outer workspace, so they are missing from the graph.

  • code: nested_workspace
  • severity: error

Cause [[cause: text]]text

  • A __Workspace object was created inside an existing workspace
  • Incorrect directory structure

Not reported when the inner workspace is composed alongside the outer one — -w repo -w repo/.qmdc, or a directory holding both. Its files are then in the result under its own workspace, so nothing is missing. This is the layout qmdc-wiki gives a repository it models.

Solution [[solution: text]]text

  1. Compose both: qmdc workspace validate -w <outer> -w <outer>/<inner>
  2. Move the nested workspace up one level (make them siblings)
  3. Change the Kind to __Namespace instead of __Workspace, if it was meant to be part of the outer workspace
  4. Delete the nested workspace

Workspace In Wrong File [[err_workspace_in_wrong_file: ValidationError]]ValidationError

A __Workspace object is defined in a file other than readme.qmd.md.

  • code: workspace_in_wrong_file
  • severity: error

Cause [[cause: text]]text

__Workspace declarations must be in readme.qmd.md (the anchor file for the workspace root). If a __Workspace object appears in any other file, the parser generates this error.

Solution [[solution: text]]text

  1. Move the __Workspace declaration to readme.qmd.md
  2. If the file is a namespace, use __Namespace instead of __Workspace

Structured In TextBlock [[err_structured_in_textblock: ValidationError]]ValidationError

Attempt to create a structured element (object/field) inside a __TextBlock.

  • code: structured_in_textblock
  • severity: error

Cause [[cause: text]]text

__TextBlock is a system object for unstructured content. It is created when:

  • The document starts with a heading without [[id]]
  • A heading without [[id]] appears at the top level (not inside an object)
  • After a code fence at the top level

Inside a __TextBlock, structured elements are forbidden:

  • Headings with [[id]] or [[id: Kind]]
  • Field lists - key: value

Examples [[examples: text]]text

Error example:

## Documentation Section

This starts a TextBlock (heading without [[id]]).

### Some Object [[my_obj]]

Error! Attempt to create an object inside a TextBlock.

Important exception — do not confuse TextBlock with an object's comment section:

A comment section is a heading without [[id]] inside an object. It is part of the object (__comments field), not a TextBlock.

# My Object [[my_obj: Kind]]

- name: Example

## Comment Section

This is a Comment inside an object, NOT a TextBlock!

### Nested [[nested_obj: NestedKind]]

This is a valid nested object, NOT an error.

In an object's comment section, a heading with [[id: Kind]] creates a nested object — this is normal behavior.

Solution [[solution: text]]text

  1. Add [[id]] to the parent heading to create an object instead of a TextBlock
  2. Move the structured content to the top level
  3. Remove [[id]] from the nested heading if it is just text

Multiple Definitions In Heading [[err_multiple_definitions: ValidationError]]ValidationError

A heading contains more than one [[...]] definition.

  • code: multiple_definitions
  • severity: error

Cause [[cause: text]]text

Each heading may contain at most one [[id]] or [[id: Kind]] definition. If a heading has two or more definitions, the parser cannot unambiguously determine which one is the object ID and which is a field. This is a syntax error.

Examples [[examples: text]]text

## Also broken [[items: array]] [[finding_array: Finding]]

Here [[items: array]] is a field definition and [[finding_array: Finding]] is an object definition. The parser does not know which is the heading's ID.

Solution [[solution: text]]text

  1. Split into two headings:
## Findings [[finding_array: Finding]]

### Items [[items: array]]

- severity: high
  1. Or use a single definition with fields:
## Also broken [[finding_array: Finding]]

- severity: high
- category: parser

Ordered List In Array [[err_ordered_list_in_array: ValidationError]]ValidationError

A numbered list (1. item) is used inside a heading-syntax array instead of a bullet list.

  • code: ordered_list_in_array
  • severity: error

Cause [[cause: text]]text

QMD.md supports only bullet lists (- item) for markdown-list arrays. Numbered lists (1. First, 2. Second) are forbidden — numbering is redundant since arrays are ordered by definition.

The numbered list content is preserved in __comments for lossless round-trip, but the parser generates an error.

Examples [[examples: text]]text

## Task [[task1: Task]]

- category: implementation

### Steps [[steps: array]]

1. First step
2. Second step
3. Third step

Solution [[solution: text]]text

Replace the numbered list with a bullet list:

### Steps [[steps: array]]

- First step
- Second step
- Third step

Or use YAML notation:

- steps: [First step, Second step, Third step]

Table In Array [[err_table_in_array: ValidationError]]ValidationError

A Markdown table is used under a primitive array field ([[field: array]]).

  • code: table_in_array
  • severity: error

Cause [[cause: text]]text

A primitive array holds scalar values, and a table has columns — there is no defined mapping from one onto the other, so the parser cannot decide what a row should become.

This differs from an OBJECT array ([[field: [Kind]]]), where a table IS valid: each data row becomes one object and the column names become its fields. The distinction is the declared field type, not the table.

The table content is preserved in __comments for lossless round-trip, but the parser generates an error.

Examples [[examples: text]]text

## Doc [[doc]]

### Tags [[tags: array]]

| a |
|---|
| 1 |

Solution [[solution: text]]text

If the values are scalars, use a bullet list:

### Tags [[tags: array]]

- one
- two

If each row is meant to be an object, declare an object array and give it a Kind:

### Tags [[tags: [Tag]]]

| name | colour |
| ---- | ------ |
| one  | red    |

Extra Table In Array [[err_extra_table_in_array: ValidationError]]ValidationError

A second Markdown table appears under one object-array heading.

  • code: extra_table_in_array
  • severity: error

Cause [[cause: text]]text

The array heading's own table is what feeds the array — each data row becomes one object, with a positional id derived from the row's index. A second table under the same heading cannot extend the array, because its rows would generate the same ids as the first table's, and the heading declares an array rather than prose, so the table describes nothing.

The table content is preserved in __comments for lossless round-trip, but the parser generates an error.

Note this applies only to the array CONTAINER's own content. A table inside an array ELEMENT is that element's content and is perfectly valid — see the Arrays section.

Examples [[examples: text]]text

## Team [[team: Group]]

### Members [[members: [User]]]

| name  |
| ----- |
| Alice |

| name |
| ---- |
| Bob  |

Solution [[solution: text]]text

Put every row in one table:

### Members [[members: [User]]]

| name  |
| ----- |
| Alice |
| Bob   |

Mixed Array [[err_mixed_array: ValidationError]]ValidationError

An array section holds something that cannot be one of its elements: an object array fed by a table AND also carrying heading elements, or a declaration inside a primitive [[field: array]].

  • code: mixed_array
  • severity: error

Cause [[cause: text]]text

An object array is written in exactly one of two forms — a table, where each data row becomes an object, or subheadings, where each subheading becomes an object. Mixing them is not supported: once the array has been built from the table, a following element heading cannot join it, so it silently becomes a plain field on the parent and its declared Kind is lost.

Note this fires only when the TABLE comes first. A table AFTER an element heading is that element's own content, which is valid — see the Arrays section.

The same code covers a PRIMITIVE array, [[field: array]], that contains a deeper heading declaring an identifier. A primitive array's elements are list items, and a heading is not one, so the declaration cannot become an element — and the heading does not become an object either, because the declared kind decides what the section is — the rule docs/format/headings.qmd.md states for text. The heading closes the array's content, exactly as prose between two lists does, and its own text is not preserved: the document is invalid and has to be fixed.

Examples [[examples: text]]text

## Team [[team: Group]]

### Members [[members: [User]]]

| name  |
| ----- |
| Alice |

#### Bob [[bob]]

- role: dev

Here bob does not join members: it becomes team.bob, a field on team, with its Kind degraded from User to __Object.

A declaration inside a primitive array:

## Session [[s: Session]]

### Closure [[clo: array]]

- one
- two

#### Task [[t5: SessionTask]]

- status: x

clo keeps exactly ["one", "two"], no object s.clo.t5 or s.t5 is created, and one mixed_array error points at the #### Task line.

Solution [[solution: text]]text

Use one form for the whole array. Either put every element in the table:

### Members [[members: [User]]]

| name  | role  |
| ----- | ----- |
| Alice |       |
| Bob   | dev   |

Or write every element as a subheading:

### Members [[members: [User]]]

#### Alice [[alice]]

#### Bob [[bob]]

- role: dev

Explicit System Type [[err_explicit_system_type: ValidationError]]ValidationError

Explicit declaration of a system type __Document, __TextBlock, or __Object in a heading.

  • code: explicit_system_type
  • severity: error

Cause [[cause: text]]text

System types __Document, __TextBlock, and __Object are created by the parser automatically only. Explicit declaration of [[id: __Document]], [[id: __TextBlock]], or [[id: __Object]] in a heading is forbidden.

Types __Workspace and __Namespace allow explicit declaration in anchor files (readme.qmd.md).

Examples [[examples: text]]text

# Test Document [[test_doc: __Document]]

- version: 1.0

Solution [[solution: text]]text

  1. Remove the system type from the heading: # Test Document [[test_doc]]
  2. Use a user-defined type: # Test Document [[test_doc: Document]]

Dangling Field [[err_dangling_field: ValidationError]]ValidationError

A heading-syntax field is declared without a parent object at a higher heading level.

  • code: dangling_field
  • severity: error

Cause [[cause: text]]text

Heading-syntax field types (text, array, yaml, json, object_array) imply a parent object at a higher heading level. If no such parent exists (field at the top level or at the same level as a sibling object), this is an error.

The parser creates an object for lossless round-trip but generates a dangling_field error.

Examples [[examples: text]]text

## Result [[result1: Finding]]

- status: done

## Summary [[summary: text]]

This is a text field at the same H2 level as Result — no parent object exists.

Here [[summary: text]] is a heading-syntax field of type text, but it is at the same H2 level as [[result1]]. There is no parent object at a higher level (H1) that could contain this field.

Solution [[solution: text]]text

  1. Add a parent object at a higher level:
# Document [[doc1]]

## Result [[result1: Finding]]

- status: done

## Summary [[summary: text]]

Summary text here.
  1. Or remove the field type and make it a regular object:
## Summary [[summary]]

- content: Summary text here.

Invalid Map Entry [[err_invalid_map_entry: ValidationError]]ValidationError

A list item inside [[field: map]] is not a valid key: value pair.

  • code: invalid_map_entry
  • severity: error

Cause [[cause: text]]text

A map field expects a bullet list with items in the format - key: value, where key is a valid QMD.md key ([a-zA-Z_][a-zA-Z0-9_]*). The error is generated when:

  • The key contains Markdown formatting (**bold**, `code`)
  • The list item does not contain a colon (- just text)
  • The item is a link (- [link](url))

Invalid items are discarded; valid pairs are kept in the map.

Examples [[examples: text]]text

# Service [[svc1]]

### env [[env: map]]

- host: localhost
- **port**: 8080
- path: /api

The line - **port**: 8080 generates an error — bold formatting makes the key invalid.

Solution [[solution: text]]text

Remove Markdown formatting from keys:

### env [[env: map]]

- host: localhost
- port: 8080
- path: /api

Broken Parent [[err_broken_parent: ValidationError]]ValidationError

Parent object not found for a dot-ID declaration.

  • code: broken_parent
  • severity: error

Cause [[cause: text]]text

A dot-ID object [[parent_path.local_id]] declares a hierarchical ID where parent_path does not resolve to an existing object in the workspace. The parent must exist for the child to attach to it.

Examples [[examples: text]]text

## Alice [[team.members.alice]]

- role: admin

If no object with __id equal to team exists (or team has no members field path), the parser cannot establish the parent relationship.

Solution [[solution: text]]text

  1. Create the parent object first: ## Team [[team]] with a members array field
  2. Fix the parent path in the dot-ID: ensure each segment resolves to an existing object
  3. Use a flat ID instead if hierarchy is not needed: [[alice]]

Ambiguous Field Reference [[err_ambiguous_field_reference: ValidationError]]ValidationError

A dot-path reference cannot be unequivocally resolved to an object or a field.

  • code: ambiguous_field_reference
  • severity: error

Cause [[cause: text]]text

A dot-path reference resolves both as an object ID and as a field-path on a parent object. The parser cannot determine whether the reference targets the object or the field.

Exemption: the error is NOT raised when the parent's field value is exactly the parser-generated child link [[#<full dotted id>]]. Every nested child produces such a field on its parent (e.g. svc gets config: "[[#svc.config]]"), so without this exemption every hierarchical parent→child reference would be flagged. Only a field whose value is anything else (a scalar, a different reference) makes the dot-path genuinely ambiguous.

Examples [[examples: text]]text

## Team [[team]]

- status: active

## Status [[team.status]]

- value: operational

Here [[#team.status]] is ambiguous — it could reference the field status on object team, or the object with hierarchical ID team.status.

Solution [[solution: text]]text

  1. Rename the child object to avoid collision with the field name
  2. Remove the field from the parent if the object is the intended target
  3. Use a non-hierarchical ID for the object: [[team_status]]

Invalid Map Content [[err_invalid_map_content: ValidationError]]ValidationError

Content inside [[field: map]] that is not a bullet list with key: value pairs.

  • code: invalid_map_content
  • severity: error

Cause [[cause: text]]text

A map field accepts only a single bullet list with - key: value items. Any other content between the map heading and the next heading at the same or higher level is an error: paragraphs, code fences, numbered lists, additional bullet lists, and deeper headings — including a deeper heading that DECLARES an identifier, which creates no object, because the declared kind decides what the section is.

Invalid content is ignored; the map is populated only from the first valid bullet list. An entry after an offending block does not join the map.

A map is a flat str -> str dictionary, so an indented sub-item is not an entry either: under a - key: with no value it is invalid_map_entry's sibling error nested_subitems and the key is dropped; under a - key: value the sub-item is dropped and the entry keeps its own value.

Examples [[examples: text]]text

````markdown example

Service [[svc1]]

env [[env: map]]

Some description paragraph.

  • host: localhost
  • port: 8080

  • numbered item

some: code
````

The paragraph, numbered list, and code fence generate invalid_map_content errors.

Solution [[solution: text]]text

Remove all content except the bullet list with key: value pairs:

```markdown

env [[env: map]]

  • host: localhost
  • port: 8080 ```

Block In Inline Field [[err_block_in_inline_field: ValidationError]]ValidationError

An indented block follows an inline field that already has a value.

  • code: block_in_inline_field
  • severity: error

Cause [[cause: text]]text

An inline field (- key: value) holds a scalar. It has no content of its own, so an indented table, list, paragraph, quote or fence placed under it belongs to nothing — the format defines no meaning for it.

This is the sibling of nested_subitems, which covers the EMPTY-value form (- key: followed by indented items). Here the value is present and the block follows it.

Not to be confused with YAML multiline (- key: | or - key: >), where the indented block IS the field's value. That is valid and is not reported.

The block content is preserved in __comments, anchored on the field and with its indentation removed, for lossless round-trip; the parser generates an error.

Examples [[examples: text]]text

```markdown

P [[p: G]]

  • lead: Ann

  • note: something

| ic | |----| | x | ```

Solution [[solution: text]]text

If the block belongs to the object, put it outside the field list:

```markdown

P [[p: G]]

  • lead: Ann
  • note: something

| ic | |----| | x | ```

If it belongs to the field, declare a heading-syntax text field instead:

```markdown

P [[p: G]]

  • lead: Ann

Note [[note: text]]

something

| ic | |----| | x | ```

Nested Subitems [[err_nested_subitems: ValidationError]]ValidationError

A nested list under an inline field (- key: followed by indented - item lines).

  • code: nested_subitems
  • severity: error

Cause [[cause: text]]text

Inline fields are flat — a list item cannot carry its own sub-list. The pattern - key: with indented - item children is rejected.

Solution [[solution: text]]text

Use a YAML array (- items: [first, second]) or heading-syntax (### Items [[items: array]] with a bullet list).

Mixed Field Keys [[err_mixed_field_keys: ValidationError]]ValidationError

An object's field list mixes valid and invalid keys.

  • code: mixed_field_keys
  • severity: error

Cause [[cause: text]]text

Valid keys match [a-zA-Z][a-zA-Z0-9_]*. When SOME items in one object's list are valid fields and others are not (spaces, hyphens, leading digits, markdown formatting in the key), the parser cannot decide whether the list is a field list or plain text.

Solution [[solution: text]]text

Fix the invalid keys (First Name: → first_name:), or convert the list to a text field if it is prose.

Unsupported Number Format [[err_unsupported_number_format: ValidationError]]ValidationError

A field value looks like a number QMD.md cannot carry.

  • code: unsupported_number_format
  • severity: error

Cause [[cause: text]]text

QMD.md's numeric grammar is deliberately narrow: -?\d+(\.\d+)?, with a magnitude between 1e-4 and 2^53-1. Two kinds of value trip this error.

A spelling the format does not define — 1e5, 1.5e-3, 2E3, .5, 5., +1, 1_000, 0x1f, 0o17. Each of these only ever worked in whichever host language happened to accept it, which is exactly why the three parsers used to disagree about them.

A value in a supported spelling but outside the range. Above 2^53-1 an integer cannot survive a round trip through a double — 9223372036854775807 comes back as a different number in a JavaScript reader — and below 1e-4 a decimal can only be written with an exponent, which the grammar has no form for.

The field keeps the text the author wrote, as a String, so nothing is lost and the document still round-trips. The error exists so the spelling is refused out loud instead of silently becoming a string.

```markdown

Config [[config: Settings]]

  • retries: 3 ← fine
  • rate: 1e-3 ← error: exponent
  • max: 9223372036854775807 ← error: beyond 2^53-1
  • epsilon: 0.00001 ← error: needs an exponent to write back
  • released: 2026-09-24 ← fine, a plain string and no error ```

Solution [[solution: text]]text

Write the value in the supported grammar (0.001 rather than 1e-3), or quote it to say a String was meant all along (- max: "9223372036854775807").

A quoted value raises no error, which is the intended escape hatch: the quotes state that the text is the value.

The error carries a hint field naming which of those two ways out applies, so the reader is told what to write rather than only what is wrong. A spelling the grammar does not define gets write a plain integer or decimal such as 42 or -1.5 (or quote the value to keep it as text); a magnitude outside the range gets magnitude outside 0.0001..9007199254740991 (quote the value to keep it as text), since quoting is then the only way out. It is the only error that carries a hint.

Invalid ID Character [[err_invalid_id_character: ValidationError]]ValidationError

A dot in a NESTED heading's explicit ID.

  • code: invalid_id_character
  • severity: error

Cause [[cause: text]]text

Dot-ID declarations ([[parent.child]]) are legal only on top-level headings. A nested child's hierarchical ID is composed automatically from its parent; an explicit dot in a nested [[id]] is a parsing error.

```markdown

Parent [[parent]]

Child [[child.invalid]] ← error: dot in nested explicit ID

```

Solution [[solution: text]]text

Use a simple local id for the nested heading ([[child]]); the dot-path (parent.child) is composed automatically.

Wrapped Field Value [[err_wrapped_field_value: ValidationError]]ValidationError

A field value, or an array element, continued on an indented second line.

  • code: wrapped_field_value
  • severity: error

Cause [[cause: text]]text

A value is written on one line. A continuation line was read three different ways — Rust joined the lines with a space, Python with a newline, TypeScript kept only the first line and dropped the rest in silence — so the same document carried three different values depending on which parser read it. The value is now the authored first line everywhere, and the continuation is reported instead of being absorbed or discarded quietly.

```markdown

Session [[s: Session]]

  • note: first line second line ```

note is first line, and one wrapped_field_value error points at the continuation's line. An element of a [[field: array]] list behaves the same way.

The two legal multiline forms are untouched, because each announces itself on the first line: YAML pipe (- key: |) and a YAML array whose bracket opens the value (- key: [).

Solution [[solution: text]]text

Put the value on one line, use the YAML pipe form for genuinely multiline text, or move it to a heading-syntax text field (### Note [[note: text]]).