Skip to content

Migration Guide: UDE v1.0 to v2.0

This guide is the complete checklist for moving a working v1.0 setup to v2.0. Every command, field name, error message and output sample below was verified against the shipped engine on 2026-08-02 — where v2.0 raises a hard error on a v1.0 input, the exact message is quoted so you can match it against your logs.

Read the Hard stop sections first: they are the changes that make a previously working configuration fail outright.

Hard stop 1: every document directory now requires sidebar.toml

This is the single most likely reason a v1.0 project stops building on v2.0.

sidebar.toml replaced the per-renderer toc_*.json files as the source of navigation truth. Delete any toc_*.json you carried over — they are no longer read by anything.

The file is mandatory and must declare at least one [[sidebar]] entry. The pipeline refuses a document directory otherwise rather than falling back to defaults, because navigation that renders successfully but wrongly is far harder to notice than a failed build. Three separate failures, all fatal:

text
Required sidebar.toml not found in <dir>. Every document directory must contain a sidebar.toml file.
sidebar.toml in <dir> declares no [[sidebar]] entries. There is no default navigation to fall back on ...
Failed to parse required sidebar.toml '<path>': <parser detail>

Place one in each <project>/ directory that has a ude_doc_config.json, including the Hugo variants. A minimal file is three lines:

toml
[[sidebar]]
type = "api_reference"
label = "API Reference (C++)"
url = "index.html"

Configuration resolves through a three-tier deep merge (global → SDK → document), so shared values live at the outer tiers and each document directory carries only its overrides.

The folder taxonomy that used to live in template JSON now sits in a [groups] table:

toml
[groups]
namespace_level = ["Classes", "Structures", "Enums"]
class_level = ["Methods", "Properties"]

The table's contents are strictly validated (extra="forbid"), so a misspelled key inside [groups] is a hard error rather than a silently empty taxonomy. [groups] has one extra tier below the usual three — the engine's own default TOML seeds it — and a [groups] table found in the document directory merges at the highest priority of all.

[[sidebar]] and [groups] behave differently, deliberately:

TableIf you omit it
[[sidebar]]Build fails. There is no default navigation at any tier.
[groups]Engine default applies. Most projects never declare it.

The asymmetry is intentional. A taxonomy default produces a sensible, uniform folder layout; a navigation default would produce a sidebar you never wrote, which renders successfully and gives you nothing to notice. So an emptysidebar.toml is not valid — it fails exactly like a missing one:

text
sidebar.toml in <dir> declares no [[sidebar]] entries. There is no default
navigation to fall back on — every document directory must define its own
[[sidebar]] table.

An explicit sidebar = [] is rejected for the same reason.

Hard stop 2: removed and renamed configuration keys

sidebar_structures_dir was removed from ude_global_config.json. It is trapped explicitly rather than ignored, so a stale key fails loudly:

text
sidebar_structures_dir is removed as of [GAP-32-Debt-2]; folder taxonomy is now sourced from sidebar.toml [groups].

Remove the key and move its content into sidebar.toml's [groups] table.

renderer.type must be a renderer family key, not a renderer class name. Configs naming a concrete class (CppHtmlODARenderer, PyHugoDefaultRenderer, …) fail with:

text
Unsupported format '<value>' in target config.

The renderer's own factory selects the language-specific subclass from collector.language, so the family key is all you supply:

renderer.typeOutput
html, static_htmlStandalone static HTML
oda_htmlODA-conventions HTML
hugo_markdown, markdown, hugoHugo Markdown
oda_hugo_markdown, oda_markdown, oda_hugoODA-conventions Hugo Markdown

Unknown keys elsewhere in ude_global_config.json are still ignored silently (extra="ignore"), and every field has a default — a config missing a field will not raise.

Typed entity models

The untyped ClassEntity has been replaced by strict Pydantic models. The change you are most likely to feel is in class fields:

  • v1.0: fields: ["int count", "string name"]
  • v2.0: fields: List[VariableModel], each with .name, .type and .docstring

If you maintain a custom renderer or any code that reads the IR, replace string parsing with attribute access. Seven typed models ship in total, covering variables, constants, enums, type aliases, parameters, overloads and methods.

Existing imports keep working. ClassEntity, NamespaceEntity and MethodEntity remain as module-level aliases of the new model classes and are explicitly maintained for external consumers — you do not have to rename imports as part of this migration.

ProjectCatalog metadata

ProjectCatalog gained project_name and version. Both are optional, defaulting to an empty string — no action is required, and neither belongs in ude_global_config.json. They are IR-level metadata populated from your document config; set project_name in ude_doc_config.json if you want it carried into the IR.

Decoupled CLI

The v1.0 flat interface is unchanged and still supported — ude --doc-config … behaves exactly as before, and ude compile is its explicit equivalent. v2.0 adds subcommands that split parsing from rendering, which is what you want if you plan to cache or archive the IR between CI stages.

Note that the IR is gzip-compressed; use a .json.gz extension.

Generate IR:

bash
ude parse --doc-config path/to/ude_doc_config.json --output-ir catalog.json.gz

Render from IR:

bash
ude render --input-ir catalog.json.gz --output ./public --format html

--output-ir is required for parse; --input-ir and --output are required for render. Both accept --global-config / -g and --doc-config / -d.

Coverage auditing and ude audit

Two new GlobalConfig fields drive the documentation coverage gate:

  • coverage_modeallow-undocumented (default, reports only) or reject-undocumented (fails the build).
  • coverage_threshold — a fraction between 0.0 and 1.0, default 1.0.

Unit mismatch, worth pinning to memory. The config field is a fraction (0.98), while the CLI flag is a human-friendly percentage (--threshold 98, valid range 0–100). Passing --threshold 0.98 is accepted but means 0.98 percent, which will pass almost anything. The CLI divides by 100 before handing the value to the gate.

--mode and --threshold override the config values when given explicitly.

Exit codes

CodeMeaning
0Audit completed; gate passed, or mode is allow-undocumented
2Gate rejected the target: reject-undocumented and coverage below threshold

Actual output format

ude audit writes a Markdown report to stdout: a summary header followed by one row per entity — not per module.

markdown
# Documentation Coverage Audit

**Total Entities:** 4948
**Documented Entities:** 969
**Overall Coverage:** 19.58%

| Type | Entity Name | Documented |
| :--- | :--- | :--- |
| Class | `ODA::Publish::PdfPublish::OdPdfPublish_Od2dGeometryBlock` | PASS |
| Method | `ODA::Publish::PdfPublish::OdPdfPublish_Od2dGeometryBlock.Format` | FAIL |

On a large SDK this is thousands of rows — redirect it to a file rather than into a CI log, and use the exit code for the pass/fail decision.

GitHub Actions integration

yaml
      - name: UDE Coverage Audit
        shell: bash -euo pipefail {0}
        env:
          PYTHONPATH: engine
        run: |
          python -m ude.cli audit \
            --doc-config path/to/ude_doc_config.json \
            --threshold 98 \
            --mode reject-undocumented

When embedding the engine as a library instead, apply_coverage_gate() raises UdeException rather than calling sys.exit(), so a host application is never killed by a coverage failure.

Also new in v2.0 (no migration action required)

These arrive automatically and need no configuration change:

  • Unified logging — one ude root logger driven by log_level / log_file. Importing ude.config without calling logging_setup() produces no output, per the standard library contract, so embedding the engine stays quiet by default.
  • L2 render cache — unchanged entities skip re-rendering on repeat builds.
  • Three-tier Doxyfile merge — key-level merge across global, SDK and document tiers, resolved via global_templates_dir.
  • Public library APIUdeOrchestrator exposes parse, render and run for embedding without going through the CLI.

Step-by-step migration checklist

  1. [ ] Add a sidebar.toml declaring at least one [[sidebar]] entry to every directory containing a ude_doc_config.json, Hugo variants included. The build fails on a missing, empty or malformed file — there is no default navigation.
  2. [ ] Delete leftover toc_*.json files; move any folder taxonomy into sidebar.toml's [groups] table.
  3. [ ] Remove sidebar_structures_dir from ude_global_config.json if present.
  4. [ ] Change any renderer.type naming a concrete renderer class to the corresponding family key from the table above.
  5. [ ] Update custom renderers and IR consumers to read VariableModel attributes (.name, .type, .docstring) instead of parsing strings. Legacy ClassEntity / NamespaceEntity / MethodEntity imports need no change.
  6. [ ] Optional: adopt ude parse / ude render if you want the IR as a separate CI artifact. ude compile and the v1.0 flat flags keep working.
  7. [ ] Optional: set coverage_mode and coverage_threshold, and add ude audit to CI — remembering the fraction-vs-percentage distinction.

Verifying the migration

bash
# 1. A v1.0 config still compiles through the flat interface
ude --doc-config path/to/ude_doc_config.json

# 2. A previously saved v1.0 IR still loads
python -c "from ude.storage import load_compressed_ir; load_compressed_ir('old_catalog.json.gz')"

# 3. Split pipeline produces the same output as a single-pass compile
ude parse  --doc-config path/to/ude_doc_config.json --output-ir /tmp/ir.json.gz
ude render --input-ir /tmp/ir.json.gz --output /tmp/split --format html
ude compile --doc-config path/to/ude_doc_config.json --output /tmp/single --format html

Related Docs: (Paths below are repository-root-relative.)

  • user-docs/docs/cli-reference.md — full reference for the subcommands introduced here
  • user-docs/docs/changelog.md — condensed v1.0 -> v2.0 release notes
  • user-docs/docs/global-settings.mdude_global_config.json field reference
  • user-docs/docs/target-settings.mdude_doc_config.json field reference